Skip to main content

Error Response Format

All JobHive API errors follow a consistent JSON structure to help you handle them programmatically:
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description",
    "details": {
      "field": "specific_field_name",
      "value": "invalid_value",
      "additional_context": "extra_information"
    }
  },
  "meta": {
    "timestamp": "2024-01-15T10:30:00Z",
    "request_id": "req_abc123def456",
    "documentation_url": "https://docs.jobhive.ai/api-reference/errors#ERROR_CODE"
  }
}

HTTP Status Codes

JobHive uses standard HTTP status codes to indicate the success or failure of API requests:
CodeDescriptionWhen Used
200OKSuccessful GET, PATCH, DELETE requests
201CreatedSuccessful POST requests (interview creation)
202AcceptedAsynchronous operations initiated
204No ContentSuccessful DELETE with no response body
CodeDescriptionCommon Causes
400Bad RequestInvalid request parameters, malformed JSON
401UnauthorizedMissing or invalid API key
403ForbiddenInsufficient permissions for operation
404Not FoundInterview ID doesn’t exist
409ConflictResource already exists or state conflict
422Unprocessable EntityValid JSON but business logic validation failed
429Too Many RequestsRate limit exceeded
CodeDescriptionHandling Strategy
500Internal Server ErrorRetry with exponential backoff
502Bad GatewayTemporary infrastructure issue, retry
503Service UnavailablePlanned maintenance, check status page
504Gateway TimeoutRequest timeout, retry with longer timeout

Common Error Types

Authentication Errors (401)

  • Missing API Key
  • Invalid API Key
  • Expired API Key
{
  "success": false,
  "error": {
    "code": "AUTHENTICATION_REQUIRED",
    "message": "API key is required. Include 'Authorization: Bearer YOUR_API_KEY' header.",
    "details": {
      "header_missing": "Authorization"
    }
  }
}
Solution: Add the Authorization header to your request
headers: {
  'Authorization': `Bearer ${process.env.JOBHIVE_API_KEY}`,
  'Content-Type': 'application/json'
}

Validation Errors (400/422)

  • Missing Required Fields
  • Invalid Field Values
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Required fields are missing",
    "details": {
      "missing_fields": ["candidate_email", "position"],
      "provided_fields": ["skills", "duration_minutes"]
    }
  }
}
Solution: Include all required fields in your request

Rate Limiting (429)

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please slow down.",
    "details": {
      "limit": 300,
      "current": 301,
      "reset_at": "2024-01-15T10:31:00Z",
      "retry_after": 60
    }
  }
}
Headers included in rate limit responses:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995260
X-RateLimit-Retry-After: 60

Resource Errors (404/409)

  • Interview Not Found
  • Invalid State Transition
{
  "success": false,
  "error": {
    "code": "INTERVIEW_NOT_FOUND",
    "message": "Interview with ID 'int_invalid123' not found",
    "details": {
      "interview_id": "int_invalid123",
      "suggestion": "Verify the interview ID is correct"
    }
  }
}

Error Handling Patterns

Basic Error Handling

async function handleJobHiveRequest(url, options) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Authorization': `Bearer ${process.env.JOBHIVE_API_KEY}`,
        'Content-Type': 'application/json',
        ...options.headers
      }
    });

    // Parse response
    const data = await response.json();

    if (!response.ok) {
      throw new JobHiveAPIError(response.status, data.error);
    }

    return data;
  } catch (error) {
    if (error instanceof JobHiveAPIError) {
      throw error; // Re-throw API errors
    }
    
    // Network or parsing errors
    throw new Error(`Request failed: ${error.message}`);
  }
}

class JobHiveAPIError extends Error {
  constructor(status, errorData) {
    super(errorData.message);
    this.name = 'JobHiveAPIError';
    this.status = status;
    this.code = errorData.code;
    this.details = errorData.details;
  }
}

// Usage
try {
  const interview = await handleJobHiveRequest('/interviews', {
    method: 'POST',
    body: JSON.stringify(interviewData)
  });
  console.log('Interview created:', interview.data.id);
} catch (error) {
  if (error instanceof JobHiveAPIError) {
    console.error(`API Error (${error.code}):`, error.message);
    console.error('Details:', error.details);
  } else {
    console.error('Request Error:', error.message);
  }
}

Retry Logic with Exponential Backoff

class RetryableJobHiveClient {
  constructor(apiKey, maxRetries = 3) {
    this.apiKey = apiKey;
    this.maxRetries = maxRetries;
    this.baseUrl = 'https://backend.jobhive.ai/v1';
  }

  async makeRequestWithRetry(endpoint, options = {}, attempt = 1) {
    try {
      const response = await fetch(`${this.baseUrl}${endpoint}`, {
        ...options,
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json',
          ...options.headers
        }
      });

      const data = await response.json();

      if (!response.ok) {
        // Check if error is retryable
        if (this.isRetryableError(response.status) && attempt <= this.maxRetries) {
          const delay = this.calculateDelay(attempt, response.headers);
          console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
          
          await this.sleep(delay);
          return this.makeRequestWithRetry(endpoint, options, attempt + 1);
        }
        
        throw new JobHiveAPIError(response.status, data.error);
      }

      return data;
    } catch (error) {
      if (error instanceof JobHiveAPIError) {
        throw error;
      }
      
      // Network errors - retry if we have attempts left
      if (attempt <= this.maxRetries) {
        const delay = this.calculateDelay(attempt);
        console.log(`Network error, retrying in ${delay}ms...`);
        
        await this.sleep(delay);
        return this.makeRequestWithRetry(endpoint, options, attempt + 1);
      }
      
      throw error;
    }
  }

  isRetryableError(status) {
    // Retry on server errors and rate limiting
    return status >= 500 || status === 429;
  }

  calculateDelay(attempt, headers = {}) {
    // Use Retry-After header if available
    const retryAfter = headers.get?.('X-RateLimit-Retry-After');
    if (retryAfter) {
      return parseInt(retryAfter) * 1000;
    }
    
    // Exponential backoff: 1s, 2s, 4s, 8s...
    return Math.min(1000 * Math.pow(2, attempt - 1), 30000);
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
const client = new RetryableJobHiveClient(process.env.JOBHIVE_API_KEY);

try {
  const interview = await client.makeRequestWithRetry('/interviews', {
    method: 'POST',
    body: JSON.stringify(interviewData)
  });
  console.log('Interview created successfully:', interview.data.id);
} catch (error) {
  console.error('All retry attempts failed:', error.message);
}

Circuit Breaker Pattern

Implement a circuit breaker to handle sustained failures gracefully:
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.threshold = threshold; // Number of failures before opening
    this.timeout = timeout; // Time to wait before trying again
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.failureCount = 0;
    this.lastFailureTime = null;
  }

  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();

    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
      console.log('Circuit breaker opened due to failures');
    }
  }
}

// Usage with JobHive client
const circuitBreaker = new CircuitBreaker(3, 30000);
const client = new RetryableJobHiveClient(process.env.JOBHIVE_API_KEY);

async function createInterviewSafely(data) {
  return circuitBreaker.execute(async () => {
    return client.makeRequestWithRetry('/interviews', {
      method: 'POST',
      body: JSON.stringify(data)
    });
  });
}

// This will fail fast if too many errors occur
try {
  const interview = await createInterviewSafely(interviewData);
  console.log('Interview created:', interview.data.id);
} catch (error) {
  if (error.message === 'Circuit breaker is OPEN') {
    console.log('Service temporarily unavailable, try again later');
  } else {
    console.error('Interview creation failed:', error.message);
  }
}

Error Recovery Strategies

Graceful Degradation

async function createInterviewWithFallback(candidateData) {
  try {
    // Try automated interview creation
    const interview = await jobhiveClient.createInterview(candidateData);
    return { success: true, type: 'automated', interview };
  } catch (error) {
    console.warn('Automated interview failed, falling back to manual process');
    
    // Log for manual follow-up
    await logManualInterviewRequest(candidateData, error);
    
    // Send notification to hiring team
    await notifyHiringTeam({
      type: 'manual_interview_required',
      candidate: candidateData,
      reason: error.message
    });
    
    return { 
      success: true, 
      type: 'manual', 
      message: 'Interview scheduled for manual processing' 
    };
  }
}
from collections import deque
import json

class InterviewQueue:
    def __init__(self):
        self.queue = deque()
        self.failed_queue = deque()
    
    def add_interview(self, interview_data):
        self.queue.append(interview_data)
    
    def process_queue(self, client):
        while self.queue:
            interview_data = self.queue.popleft()
            
            try:
                result = client.make_request_with_retry('POST', '/interviews', 
                                                      json=interview_data)
                print(f"✅ Successfully created interview: {result['data']['id']}")
                
            except JobHiveAPIError as e:
                if e.code in ['RATE_LIMIT_EXCEEDED', 'SERVICE_UNAVAILABLE']:
                    # Put back in queue for retry
                    self.queue.appendleft(interview_data)
                    print(f"⏳ Rate limited, pausing processing...")
                    break
                else:
                    # Permanent failure
                    self.failed_queue.append({
                        'data': interview_data,
                        'error': {'code': e.code, 'message': e.message}
                    })
                    print(f"❌ Permanent failure: {e.message}")
    
    def export_failed_interviews(self, filename):
        with open(filename, 'w') as f:
            json.dump(list(self.failed_queue), f, indent=2)
        print(f"💾 Exported {len(self.failed_queue)} failed interviews to {filename}")

# Usage
queue = InterviewQueue()

# Add interviews to queue
for candidate in candidate_list:
    queue.add_interview({
        'candidate_email': candidate['email'],
        'position': candidate['position'],
        'skills': candidate['skills']
    })

# Process with error handling
try:
    queue.process_queue(client)
except Exception as e:
    print(f"Queue processing stopped: {e}")
    queue.export_failed_interviews('failed_interviews.json')

Error Monitoring and Alerting

class ErrorMonitor {
  constructor() {
    this.errorCounts = new Map();
    this.alertThresholds = {
      'RATE_LIMIT_EXCEEDED': 5,
      'VALIDATION_ERROR': 10,
      'SERVICE_UNAVAILABLE': 3
    };
  }

  recordError(error) {
    const errorCode = error.code || 'UNKNOWN_ERROR';
    const count = this.errorCounts.get(errorCode) || 0;
    this.errorCounts.set(errorCode, count + 1);

    // Check if we need to send an alert
    const threshold = this.alertThresholds[errorCode] || 20;
    if (count + 1 >= threshold) {
      this.sendAlert(errorCode, count + 1);
    }

    // Log error details
    console.error(`Error recorded: ${errorCode} (count: ${count + 1})`);
    console.error('Error details:', error);
  }

  async sendAlert(errorCode, count) {
    const alert = {
      service: 'JobHive API',
      error_code: errorCode,
      count: count,
      timestamp: new Date().toISOString(),
      severity: this.getSeverity(errorCode)
    };

    // Send to monitoring service (e.g., PagerDuty, Slack)
    await this.notifyOpsTeam(alert);
  }

  getSeverity(errorCode) {
    const highSeverity = ['SERVICE_UNAVAILABLE', 'AUTHENTICATION_REQUIRED'];
    return highSeverity.includes(errorCode) ? 'HIGH' : 'MEDIUM';
  }

  async notifyOpsTeam(alert) {
    // Example: Send to Slack webhook
    try {
      await fetch(process.env.SLACK_WEBHOOK_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          text: `🚨 JobHive API Alert: ${alert.error_code}`,
          attachments: [{
            color: alert.severity === 'HIGH' ? 'danger' : 'warning',
            fields: [
              { title: 'Error Code', value: alert.error_code, short: true },
              { title: 'Count', value: alert.count, short: true },
              { title: 'Severity', value: alert.severity, short: true },
              { title: 'Time', value: alert.timestamp, short: true }
            ]
          }]
        })
      });
    } catch (e) {
      console.error('Failed to send alert:', e);
    }
  }

  getErrorReport() {
    return {
      total_errors: Array.from(this.errorCounts.values()).reduce((a, b) => a + b, 0),
      error_breakdown: Object.fromEntries(this.errorCounts),
      timestamp: new Date().toISOString()
    };
  }
}

// Usage
const errorMonitor = new ErrorMonitor();

try {
  const interview = await createInterview(data);
} catch (error) {
  errorMonitor.recordError(error);
  throw error; // Re-throw for local handling
}

Best Practices Summary

Proactive Error Handling

Implementation Checklist
  • Validate input data before API calls
  • Implement retry logic with exponential backoff
  • Use circuit breakers for sustained failures
  • Monitor error rates and patterns

User Experience

UX Considerations
  • Provide clear error messages to users
  • Implement graceful degradation
  • Show loading states during retries
  • Offer alternative actions when possible

Monitoring & Alerting

Observability Setup
  • Log all errors with context
  • Set up alerts for critical errors
  • Track error trends over time
  • Generate regular error reports

Recovery Strategies

Resilience Patterns
  • Queue failed requests for retry
  • Implement manual fallback processes
  • Use multiple API keys for redundancy
  • Plan for maintenance windows
Error Prevention: The best error handling strategy is preventing errors in the first place. Always validate input data, test thoroughly, and monitor your integration continuously.
Support Resources: For persistent issues, contact our support team at [email protected] with your request IDs and error logs for faster resolution.