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
401UnauthorizedMissing or invalid admin secret
403ForbiddenIP banned, CSRF validation failed
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.

401 Unauthorized​

Invalid admin secret:

{
"detail": "Invalid admin credentials"
}

Solution: Use correct X-Admin-Secret header value. A missing header returns "Admin authentication required".

403 Forbidden​

IP banned:

{
"error": "ip_banned",
"message": "Your IP has been temporarily banned due to repeated rate limit violations",
"retry_after": 3540
}

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

CSRF validation failed:

{
"error": "csrf_validation_failed",
"message": "Invalid or missing CSRF token"
}

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

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:

{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded: 10 per 1 minute",
"retry_after": 60,
"violations": 1,
"ban_threshold": 100
}

Headers:

Retry-After: 60
X-RateLimit-Limit: 10 per 1 minute

Solution: Wait for the number of seconds in Retry-After, then retry.

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 as long as the server asks
wait_time = int(response.headers.get('Retry-After', 60))
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​

Only 429 responses carry rate limit headers. Use Retry-After to decide how long to wait:

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

if response.status_code == 429:
wait_seconds = int(response.headers.get('Retry-After', 60))
print(f"Rate limited, retrying in {wait_seconds} seconds")
time.sleep(wait_seconds)

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​