Skip to main content

Error Handling

ChemAudit API uses standard HTTP status codes and returns detailed error messages to help you debug issues.

Error Response Format

Simple Errors

For simple errors, the response includes a detail message:

{
"detail": "Job abc123 not found"
}

Detailed Errors

For validation and parsing errors, the response includes structured error information:

{
"detail": {
"error": "Failed to parse molecule",
"errors": ["Invalid SMILES string"],
"warnings": [],
"format_detected": "smiles"
}
}
FieldDescription
errorHigh-level error message
errorsList of specific error messages
warningsList of warnings (non-fatal)
format_detectedDetected input format (if applicable)

HTTP Status Codes

CodeDescriptionCommon Causes
200SuccessRequest completed successfully
201CreatedResource created (e.g., API key)
204No ContentSuccessful deletion
400Bad RequestInvalid input, unsupported format, file too large
403ForbiddenIP banned, CSRF validation failed, unauthorized
404Not FoundJob ID not found, API key not found, endpoint doesn't exist
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error
504Gateway TimeoutRequest took too long (async validation timeout)

Common Error Scenarios

400 Bad Request

Invalid SMILES:

{
"detail": {
"error": "Failed to parse molecule",
"errors": ["Invalid SMILES string: C1CCC1 (unclosed ring)"],
"format_detected": "smiles"
}
}

Solution: Fix the SMILES syntax error.

File too large:

{
"detail": "File size (600 MB) exceeds limit (500 MB)"
}

Solution: Use a larger deployment profile or split the file.

Unsupported format:

{
"detail": "Unsupported file format: .xyz"
}

Solution: Convert to SDF, CSV, or other supported formats.

403 Forbidden

IP banned:

{
"detail": "IP address banned due to excessive rate limit violations"
}

Solution: Wait for the ban to expire (default: 60 minutes) or contact admin.

CSRF validation failed:

{
"detail": "CSRF validation failed"
}

Solution: Include valid CSRF token in X-CSRF-Token header.

Invalid admin secret:

{
"detail": "Invalid admin secret"
}

Solution: Use correct X-Admin-Secret header value.

404 Not Found

Job not found:

{
"detail": "Job abc123 not found"
}

Solution: Verify job ID is correct and job exists.

Endpoint not found:

{
"detail": "Not Found"
}

Solution: Check API path and HTTP method.

429 Too Many Requests

Rate limit exceeded:

{
"detail": "Rate limit exceeded. Try again in 42 seconds."
}

Headers:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706500842

Solution: Wait and retry, or use API key authentication for higher limits.

500 Internal Server Error

Unexpected error:

{
"detail": "Internal server error. Please try again later."
}

Solution: Check backend logs, retry request, report if persistent.

504 Gateway Timeout

Async validation timeout:

{
"detail": "Validation timeout after 30 seconds"
}

Solution: Molecule may be too complex. Try with standard /validate endpoint or increase timeout.

Error Handling Best Practices

Check Status Codes

Always check HTTP status codes before parsing response:

response = requests.post(url, json=data)

if response.status_code == 200:
result = response.json()
# Process success
elif response.status_code == 400:
error = response.json()
print(f"Invalid input: {error['detail']}")
elif response.status_code == 429:
print("Rate limited, waiting...")
time.sleep(60)
# Retry
else:
print(f"Error {response.status_code}: {response.text}")

Implement Retry Logic

For transient errors (429, 500, 504), implement exponential backoff:

import time
import requests

def validate_with_retry(molecule, max_retries=3):
for attempt in range(max_retries):
try:
response = requests.post(
"http://localhost:8001/api/v1/validate",
json={"molecule": molecule},
timeout=30
)

if response.status_code == 200:
return response.json()
elif response.status_code == 429:
# Rate limited, wait and retry
wait_time = 2 ** attempt # Exponential backoff
time.sleep(wait_time)
continue
elif response.status_code >= 500:
# Server error, retry
time.sleep(2 ** attempt)
continue
else:
# Client error, don't retry
response.raise_for_status()

except requests.exceptions.Timeout:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)

raise Exception("Max retries exceeded")

Parse Error Details

Extract useful information from error responses:

response = requests.post(url, json=data)

if not response.ok:
error_data = response.json()

if isinstance(error_data['detail'], dict):
# Structured error
print(f"Error: {error_data['detail']['error']}")
for err in error_data['detail']['errors']:
print(f" - {err}")
else:
# Simple error
print(f"Error: {error_data['detail']}")

Handle Rate Limits

Use rate limit headers to avoid hitting limits:

response = requests.post(url, json=data)

remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
if remaining < 5:
print("Warning: approaching rate limit")
# Slow down requests

reset_time = int(response.headers.get('X-RateLimit-Reset', 0))
# Schedule next request after reset time

Validate Input Locally

Catch errors early by validating input before making API requests:

def is_valid_smiles(smiles):
# Basic validation
if len(smiles) > 10000:
return False, "SMILES too long"

if not smiles.strip():
return False, "Empty SMILES"

# More sophisticated validation with RDKit
try:
from rdkit import Chem
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return False, "Invalid SMILES"
except:
return False, "SMILES parsing error"

return True, None

# Use before API call
valid, error = is_valid_smiles(user_input)
if not valid:
print(f"Input error: {error}")
else:
response = requests.post(...)

Next Steps