Skip to main content

Error Handling

The THEARY Background Check API uses conventional HTTP response codes and returns detailed error information to help you troubleshoot issues.

HTTP Status Codes

The API uses the following HTTP status codes:
Status CodeDescription
200Success - Request completed successfully
201Created - Resource created successfully
400Bad Request - Invalid parameters or missing required fields
401Unauthorized - Invalid or missing authentication
404Not Found - Resource doesn’t exist
500Internal Server Error - Something went wrong on our end

Error Response Format

All error responses follow a consistent format:
{
  "statusCode": 400,
  "message": "Validation failed",
  "error": "Bad Request"
}
For validation errors with multiple issues:
{
  "statusCode": 400,
  "message": [
    "Criminal check jurisdictions are required when requesting criminal background checks",
    "proposedSalary must be a number"
  ],
  "error": "Bad Request"
}

Common Error Scenarios

Authentication Errors (401)

Missing Authorization Header:
{
  "statusCode": 401,
  "message": "Unauthorized"
}
Invalid or Expired Token:
{
  "statusCode": 401,
  "message": "Invalid or expired token"
}

Validation Errors (400)

Missing Required Fields:
{
  "statusCode": 400,
  "message": ["applicant.firstName should not be empty", "applicant.ssn should not be empty", "entityName should not be empty"],
  "error": "Bad Request"
}
Invalid Criminal Check Configuration:
{
  "statusCode": 400,
  "message": "Criminal check jurisdictions are required when requesting criminal background checks",
  "error": "Bad Request"
}
Invalid Date Format:
{
  "statusCode": 400,
  "message": ["employmentDates.startDate must be a valid date in YYYY-MM-DD format"],
  "error": "Bad Request"
}

Resource Not Found (404)

Order Not Found:
{
  "error": "Order not found",
  "orderId": "ord_123e4567-e89b-12d3-a456-426614174000"
}
Search Not Found:
{
  "error": "Search not found",
  "orderId": "ord_123e4567-e89b-12d3-a456-426614174000",
  "searchId": "search_456e7890-e89b-12d3-a456-426614174001"
}

Error Handling Best Practices

1. Always Check Status Codes

const response = await fetch('https://api.theary.ai/background-check/v1/orders', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(orderData),
})

if (!response.ok) {
  const errorData = await response.json()
  console.error('API Error:', errorData)

  // Handle specific error types
  switch (response.status) {
    case 400:
      console.error('Validation Error:', errorData.message)
      break
    case 401:
      console.error('Authentication Error - Check your token')
      break
    case 404:
      console.error('Resource not found')
      break
    default:
      console.error('Unexpected error:', response.status)
  }

  throw new Error(`API Error: ${response.status}`)
}

const data = await response.json()

2. Handle Validation Errors

import requests

try:
    response = requests.post(url, headers=headers, json=payload)
    response.raise_for_status()
    return response.json()
except requests.exceptions.HTTPError as e:
    if e.response.status_code == 400:
        error_data = e.response.json()
        if isinstance(error_data.get('message'), list):
            for validation_error in error_data['message']:
                print(f"Validation Error: {validation_error}")
        else:
            print(f"Error: {error_data.get('message')}")
    elif e.response.status_code == 401:
        print("Authentication failed - check your JWT token")
    else:
        print(f"HTTP Error {e.response.status_code}: {e.response.text}")
    raise

3. Implement Retry Logic

For transient errors (500, network issues), implement exponential backoff:
async function createOrderWithRetry(orderData, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('https://api.theary.ai/background-check/v1/orders', {
        method: 'POST',
        headers: {
          Authorization: 'Bearer YOUR_JWT_TOKEN',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(orderData),
      })

      if (response.ok) {
        return await response.json()
      }

      // 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 (attempt === maxRetries) {
        throw new Error(`Server Error after ${maxRetries} attempts: ${response.status}`)
      }

      // Wait before retry (exponential backoff)
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000))
    } catch (error) {
      if (attempt === maxRetries) throw error
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000))
    }
  }
}

Troubleshooting Common Issues

Issue: “Criminal check jurisdictions are required”

Cause: You included CRIMINAL in searchTypes but didn’t provide criminalCheck configuration. Solution: Add the criminalCheck object:
{
  "searchTypes": [{ "searchType": "CRIMINAL", "searchTitle": "Criminal Background Check" }],
  "criminalCheck": {
    "jurisdictions": ["federal", "state"]
  }
}

Issue: “Address validation failed”

Cause: Invalid address configuration - exactly one address must be current (endDate: null). Solution: Ensure one address has endDate: null:
{
  "addresses": [
    {
      "addressType": "home",
      "addressLine1": "123 Main St",
      "addressCity": "San Francisco",
      "addressState": "CA",
      "addressZipCode": "94102",
      "startDate": "2020-01-01",
      "endDate": null // Current address
    }
  ]
}

Issue: “Invalid date format”

Cause: Dates must be in YYYY-MM-DD format. Solution: Use proper date format:
{
  "employmentDates": {
    "startDate": "2020-01-15", // ✅ Correct
    "endDate": "2023-06-30" // ✅ Correct
  }
}

Workflow Terminal Conditions

The API automatically handles terminal conditions that stop workflow progression:

Research Phase Terminal Conditions

  • No contacts found: Triggers verification.action_required with HUMAN_ESCALATION reason code
  • Third-party record detected: Triggers verification.action_required with THIRD_PARTY_RECORD reason code (includes structured contact details)

Outbound Phase Terminal Conditions

  • Transient retry cap reached: After 3 transient retries per channel (voice/email), search is reassigned to human escalation with HUMAN_ESCALATION reason code
  • Workflow failure: After max retries, workflow is terminalized with appropriate reason code (SYSTEM_FAILURE or UPSTREAM_ISSUE)

SLA and Automatic Reassignment

The system automatically monitors searches and reassigns them to human escalation when necessary:
  • Frequency: Checks run every 4 hours (system-level, not configurable)
  • Search SLA: Default 24 hours (tenant-configurable via policy.global.searchSlaMinutes)
  • Reassignment Window: Default 48 hours, allows 2 outbound attempts before escalation (tenant-configurable via policy.global.reassignmentWindowMinutes)
  • Weekend Adjustment: All deadlines (SLA and reassignment) are automatically extended if they fall on weekends (Saturday/Sunday) to the next Monday (system behavior, not configurable)
  • Reassignment Trigger: Searches are reassigned when they exceed either the search SLA or reach the reassignment window (whichever comes first)
  • Reason Code: Reassigned searches receive HUMAN_ESCALATION with detailed metadata
  • Contact Plan Inclusion: Optionally include research contact plan data in escalation webhooks (tenant-configurable via policy.global.includeContactPlanInEscalation, default: false)
Example Weekend Adjustment:
  • Search created Friday 5 PM
  • 24-hour SLA would be Saturday 5 PM
  • Automatically extended to Monday 5 PM
  • Ensures consistent handling regardless of when searches are created

Configuring SLA and Reassignment Settings

These settings are configured at the tenant level via the unified policy configuration:
{
  "policy": {
    "global": {
      "searchSlaMinutes": 1440,
      "reassignmentWindowMinutes": 2880,
      "includeContactPlanInEscalation": false
    }
  }
}
Configuration Parameters:
ParameterTypeDefaultDescription
searchSlaMinutesnumber1440Search SLA timeout in minutes (default: 24 hours). Searches exceeding this are reassigned to human escalation.
reassignmentWindowMinutesnumber2880Reassignment window in minutes (default: 48 hours). Allows 2 outbound attempts before escalation.
includeContactPlanInEscalationbooleanfalseWhether to include contact plan research data in HUMAN_ESCALATION webhooks. Provides operators with research context for follow-up.
Note: Both searchSlaMinutes and reassignmentWindowMinutes are weekend-adjusted automatically. The system uses whichever threshold is reached first when determining automatic reassignment.

Important Notes

  • When a workflow is terminalized, no further outbound attempts are made
  • Email fallback is prevented if workflow is terminalized to avoid duplicate communications
  • Terminal conditions are idempotent - if terminalization fails, workflow still stops to prevent incorrect state transitions
  • Automatic reassignment is also idempotent - searches already reassigned are not re-escalated (no duplicate webhooks)
  • Contact plan research data may be included in HUMAN_ESCALATION webhooks when includeContactPlanInEscalation is enabled (opt-in per tenant, default: false)
  • Retry cap failures now properly escalate to human review instead of being masked by communication errors

Getting Help

If you encounter issues not covered here:
  1. Check the API Reference for endpoint-specific details
  2. Verify your request format matches the examples
  3. Review Webhook Events for terminal condition details
  4. Contact support at [email protected]