Skip to main content

Error Response Format

All error responses follow a consistent format:
{
  "success": false,
  "message": "Human-readable error description",
  "errors": {
    "field_name": "Specific error details",
    "general": "General error information"
  }
}

HTTP Status Codes

CodeNameDescription
200OKRequest succeeded (check success field)
400Bad RequestInvalid parameters or request body
401UnauthorizedMissing or invalid API key
403ForbiddenAccess denied or quota exceeded
404Not FoundResource doesn’t exist
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error
502Bad GatewayUpstream service unavailable
503Service UnavailableTemporary maintenance

Common Errors

400 Bad Request

Invalid request parameters:
{
  "success": false,
  "message": "Invalid request parameters",
  "errors": {
    "q": "Query parameter is required",
    "page_size": "Must be between 1 and 100"
  }
}
Solutions:
  • Check required parameters are provided
  • Validate parameter formats and ranges
  • Ensure JSON body is properly formatted

401 Unauthorized

Authentication failed:
{
  "success": false,
  "message": "Authentication failed",
  "errors": {
    "authentication": "Invalid or expired API key"
  }
}
Solutions:
  • Verify API key is correct
  • Check header is x-api-key (lowercase)
  • Ensure key hasn’t been revoked
  • Generate a new key if needed

403 Forbidden

Access denied:
{
  "success": false,
  "message": "Access denied",
  "errors": {
    "authorization": "Your plan does not include access to this endpoint"
  }
}
Common causes:
  • Endpoint requires higher subscription tier
  • Daily quota exceeded
  • Account suspended
  • IP blocked by Cloudflare

404 Not Found

Resource doesn’t exist:
{
  "success": false,
  "message": "Resource not found",
  "errors": {
    "resource": "Victim log 'vic_123' not found"
  }
}
Solutions:
  • Verify resource ID is correct
  • Check resource hasn’t been deleted
  • Ensure you have access to the resource

429 Too Many Requests

Rate limit exceeded:
{
  "success": false,
  "message": "Rate limit exceeded",
  "errors": {
    "rate_limit": "Too many requests. Retry after 1 second."
  },
  "retry_after": 1
}
Solutions:
  • Wait for retry_after seconds
  • Implement exponential backoff
  • Reduce request frequency

Implementing Error Handling

import requests
from typing import Optional

class OathNetError(Exception):
    """Base exception for OathNet API errors."""
    def __init__(self, message: str, status_code: int, errors: dict = None):
        self.message = message
        self.status_code = status_code
        self.errors = errors or {}
        super().__init__(self.message)

class AuthenticationError(OathNetError):
    """401 Unauthorized errors."""
    pass

class AuthorizationError(OathNetError):
    """403 Forbidden errors."""
    pass

class NotFoundError(OathNetError):
    """404 Not Found errors."""
    pass

class RateLimitError(OathNetError):
    """429 Too Many Requests errors."""
    def __init__(self, message, retry_after: int = 1):
        super().__init__(message, 429)
        self.retry_after = retry_after

class ServerError(OathNetError):
    """5xx Server errors."""
    pass

def handle_response(response: requests.Response) -> dict:
    """Handle API response and raise appropriate exceptions."""
    try:
        data = response.json()
    except ValueError:
        raise ServerError("Invalid JSON response", response.status_code)

    # Check HTTP status
    if response.status_code == 401:
        raise AuthenticationError(
            data.get("message", "Authentication failed"),
            401,
            data.get("errors", {})
        )

    if response.status_code == 403:
        raise AuthorizationError(
            data.get("message", "Access denied"),
            403,
            data.get("errors", {})
        )

    if response.status_code == 404:
        raise NotFoundError(
            data.get("message", "Resource not found"),
            404,
            data.get("errors", {})
        )

    if response.status_code == 429:
        retry_after = data.get("retry_after", 1)
        raise RateLimitError(
            data.get("message", "Rate limit exceeded"),
            retry_after
        )

    if response.status_code >= 500:
        raise ServerError(
            data.get("message", "Server error"),
            response.status_code,
            data.get("errors", {})
        )

    # Check success field for 200 responses
    if not data.get("success", True):
        raise OathNetError(
            data.get("message", "Request failed"),
            response.status_code,
            data.get("errors", {})
        )

    return data

# Usage
try:
    response = requests.get(
        "https://oathnet.org/api/service/search-breach",
        params={"q": "user@example.com"},
        headers={"x-api-key": API_KEY}
    )
    data = handle_response(response)
    print(f"Found {data['data']['results_found']} results")

except AuthenticationError:
    print("Check your API key")
except AuthorizationError as e:
    print(f"Upgrade required: {e.message}")
except RateLimitError as e:
    print(f"Rate limited. Retry in {e.retry_after}s")
except NotFoundError as e:
    print(f"Not found: {e.message}")
except ServerError:
    print("Server error. Try again later.")
except OathNetError as e:
    print(f"API error: {e.message}")

Error Recovery Strategies

For transient errors (429, 5xx), retry with increasing delays:
import time
import random

def retry_with_backoff(func, max_retries=5):
    for attempt in range(max_retries):
        try:
            return func()
        except RateLimitError as e:
            if attempt == max_retries - 1:
                raise
            delay = e.retry_after + random.uniform(0, 1)
            time.sleep(delay)
        except ServerError:
            if attempt == max_retries - 1:
                raise
            delay = (2 ** attempt) + random.uniform(0, 1)
            time.sleep(delay)
Handle errors without crashing:
def safe_search(query):
    try:
        return api_call(query)
    except NotFoundError:
        return {"data": {"results": []}}  # Empty result
    except RateLimitError:
        queue_for_later(query)  # Process later
        return None
    except AuthorizationError:
        log_upgrade_needed()
        return None
Stop making requests when errors are frequent:
class CircuitBreaker:
    def __init__(self, failure_threshold=5, reset_timeout=60):
        self.failures = 0
        self.threshold = failure_threshold
        self.reset_timeout = reset_timeout
        self.last_failure = None
        self.state = "closed"

    def call(self, func):
        if self.state == "open":
            if time.time() - self.last_failure > self.reset_timeout:
                self.state = "half-open"
            else:
                raise Exception("Circuit breaker is open")

        try:
            result = func()
            self.failures = 0
            self.state = "closed"
            return result
        except (RateLimitError, ServerError) as e:
            self.failures += 1
            self.last_failure = time.time()
            if self.failures >= self.threshold:
                self.state = "open"
            raise
Log errors for debugging and monitoring:
import logging

logger = logging.getLogger("oathnet")

def logged_api_call(query):
    try:
        response = api_call(query)
        logger.info(f"Success: {query}, lookups_left={response['lookups_left']}")
        return response
    except OathNetError as e:
        logger.error(f"API error: {e.message}", extra={
            "status_code": e.status_code,
            "errors": e.errors,
            "query": query
        })
        raise

Validation Errors

For requests with invalid data, you’ll receive field-specific errors:
{
  "success": false,
  "message": "Validation failed",
  "errors": {
    "email": "Invalid email format",
    "page_size": "Must be between 1 and 100",
    "from": "Invalid date format. Use YYYY-MM-DD"
  }
}
Handle validation errors by checking the errors object:
try:
    response = api_call()
except OathNetError as e:
    if e.status_code == 400:
        for field, message in e.errors.items():
            print(f"  {field}: {message}")

Cloudflare Errors

Sometimes Cloudflare may block requests. You’ll receive an HTML response instead of JSON:
def handle_cloudflare(response):
    if "text/html" in response.headers.get("Content-Type", ""):
        if response.status_code == 403:
            raise Exception("Blocked by Cloudflare. Check your IP or try later.")
        if response.status_code == 503:
            raise Exception("Service temporarily unavailable. Cloudflare protection active.")
    return response

Next Steps