Hercle

Errors

Our API uses conventional HTTP response codes to indicate the success or failure of requests. Codes in the 2xx range indicate success, codes in the 4xx range indicate an error with the request, and codes in the 5xx range indicate an error with our servers.

HTTP Status Codes

2xx Success

  • 200 OK - Request succeeded
  • 201 Created - Resource created successfully
  • 204 No Content - Request succeeded with no response body

4xx Client Errors

  • 400 Bad Request - Invalid request parameters
  • 401 Unauthorized - Invalid or missing API key
  • 403 Forbidden - Insufficient permissions
  • 404 Not Found - Resource doesn't exist
  • 409 Conflict - Request conflicts with current state
  • 422 Unprocessable Entity - Valid request but failed validation
  • 429 Too Many Requests - Rate limit exceeded

5xx Server Errors

  • 500 Internal Server Error - Something went wrong on our end
  • 502 Bad Gateway - Temporary server error
  • 503 Service Unavailable - Temporary server overload

Error Response Format

All errors return a consistent JSON structure:

{
  "error": {
    "code": "invalid_request",
    "message": "Missing required parameter: email",
    "param": "email",
    "type": "invalid_request_error"
  }
}

Error Object Fields

  • code - Machine-readable error identifier
  • message - Human-readable error description
  • param - The parameter that caused the error (when applicable)
  • type - The error category

Common Error Codes

Authentication Errors

authentication_required

{
  "error": {
    "code": "authentication_required",
    "message": "No API key provided",
    "type": "authentication_error"
  }
}

invalid_api_key

{
  "error": {
    "code": "invalid_api_key",
    "message": "Invalid API key provided: eyJhb...",
    "type": "authentication_error"
  }
}

Validation Errors

missing_parameter

{
  "error": {
    "code": "missing_parameter",
    "message": "Missing required parameter: amount",
    "param": "amount",
    "type": "invalid_request_error"
  }
}

invalid_parameter

{
  "error": {
    "code": "invalid_parameter",
    "message": "Invalid currency: must be a valid ISO 4217 code",
    "param": "currency",
    "type": "invalid_request_error"
  }
}

Resource Errors

resource_not_found

{
  "error": {
    "code": "resource_not_found",
    "message": "No account found with id: acc_123",
    "param": "id",
    "type": "invalid_request_error"
  }
}

insufficient_funds

{
  "error": {
    "code": "insufficient_funds",
    "message": "The source account does not have sufficient funds",
    "type": "card_error"
  }
}

Rate Limiting

rate_limit_exceeded

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Please wait before making another request",
    "type": "rate_limit_error"
  }
}

Error Handling Best Practices

1. Always check status codes

const response = await fetch('/v1/accounts', {
  headers: { Authorization: `Bearer ${apiKey}` },
});

if (!response.ok) {
  const error = await response.json();
  throw new Error(`API Error: ${error.error.message}`);
}

const data = await response.json();

2. Handle specific error codes

try {
  const account = await createAccount(data);
} catch (error) {
  switch (error.code) {
    case 'missing_parameter':
      // Show validation error to user
      break;
    case 'authentication_required':
      // Redirect to login
      break;
    case 'rate_limit_exceeded':
      // Implement exponential backoff
      break;
    default:
      // Log unexpected error
      console.error('Unexpected error:', error);
  }
}

3. Implement retry logic

async function makeRequest(url, options, retries = 3) {
  try {
    const response = await fetch(url, options);

    if (response.status === 429) {
      // Rate limited - wait and retry
      const retryAfter = response.headers.get('Retry-After') || '1';
      await new Promise((resolve) =>
        setTimeout(resolve, parseInt(retryAfter) * 1000)
      );

      if (retries > 0) {
        return makeRequest(url, options, retries - 1);
      }
    }

    if (response.status >= 500 && retries > 0) {
      // Server error - exponential backoff
      await new Promise((resolve) => setTimeout(resolve, (4 - retries) * 1000));
      return makeRequest(url, options, retries - 1);
    }

    return response;
  } catch (error) {
    if (retries > 0) {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      return makeRequest(url, options, retries - 1);
    }
    throw error;
  }
}

4. Log errors properly

function logError(error, context) {
  const logData = {
    timestamp: new Date().toISOString(),
    error_code: error.code,
    error_message: error.message,
    error_type: error.type,
    context: context,
    // Don't log sensitive data like API keys
  };

  console.error('API Error:', logData);

  // Send to monitoring service
  // analytics.track('api_error', logData);
}

Webhooks and Errors

When webhook delivery fails, we'll retry with exponential backoff:

  • Immediate retry
  • 1 minute later
  • 5 minutes later
  • 30 minutes later
  • 2 hours later
  • 6 hours later

After 6 failed attempts, we'll disable the webhook endpoint and send you a notification.

Testing Error Scenarios

Use these test values to simulate different error conditions:

Test API Key for Errors

  • eyJhb...error_400 - Always returns 400 Bad Request
  • eyJhb...error_401 - Always returns 401 Unauthorized
  • eyJhb...error_500 - Always returns 500 Internal Server Error

Test Email Addresses

  • error@400.com - Returns 400 validation error
  • error@404.com - Returns 404 not found error
  • error@429.com - Returns 429 rate limit error

These test scenarios help you build robust error handling before going live.