Error Handling¶
How to handle errors from the Tajiri Vision API.
HTTP Status Codes¶
| Code | Name | Description |
|---|---|---|
200 |
OK | Request successful |
400 |
Bad Request | Invalid input (file type, size, parameters) |
422 |
Unprocessable Entity | Validation error |
429 |
Too Many Requests | Rate limit exceeded |
500 |
Internal Server Error | Server-side error |
503 |
Service Unavailable | AI service temporarily unavailable |
Error Response Format¶
All errors return a JSON object with a detail field:
Common Errors¶
400 - Invalid File Type¶
{
"detail": "Type de fichier non supporté. Utilisez: image/jpeg, image/png, image/webp, image/heic, image/heif"
}
Cause: Uploaded file is not a supported image format.
Solution: Ensure your image is JPEG, PNG, WebP, or HEIC format.
# Check file type before uploading
import mimetypes
mime_type, _ = mimetypes.guess_type("plant.jpg")
if mime_type not in ["image/jpeg", "image/png", "image/webp", "image/heic"]:
print(f"Unsupported file type: {mime_type}")
400 - File Too Large¶
Cause: Image file exceeds 10MB limit.
Solution: Compress or resize the image before uploading.
from PIL import Image
import io
def compress_image(image_path, max_size_mb=10):
"""Compress image to fit under max_size_mb."""
img = Image.open(image_path)
# Convert to RGB if needed
if img.mode in ("RGBA", "P"):
img = img.convert("RGB")
# Reduce quality until under limit
quality = 95
while True:
buffer = io.BytesIO()
img.save(buffer, format="JPEG", quality=quality)
size_mb = buffer.tell() / (1024 * 1024)
if size_mb <= max_size_mb or quality <= 10:
buffer.seek(0)
return buffer
quality -= 10
422 - Validation Error¶
{
"detail": [
{
"loc": ["body", "language"],
"msg": "value is not a valid enumeration member",
"type": "type_error.enum"
}
]
}
Cause: Invalid parameter value.
Solution: Check parameter values match the expected types and enums.
# Valid enum values
VALID_LANGUAGES = ["en", "fr", "sw", "es", "pt", "it"]
VALID_DETAIL_LEVELS = ["simple", "standard", "expert"]
# Validate before sending
if language not in VALID_LANGUAGES:
raise ValueError(f"Invalid language. Use one of: {VALID_LANGUAGES}")
429 - Rate Limit Exceeded¶
Headers:
Cause: More than 30 requests in 1 minute from your IP.
Solution: Implement retry logic with exponential backoff.
import time
import requests
def diagnose_with_retry(url, files, data, max_retries=3):
"""Make request with automatic retry on rate limit."""
for attempt in range(max_retries):
response = requests.post(url, files=files, data=data)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 30))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
return response
raise Exception("Max retries exceeded")
500 - Internal Server Error¶
Cause: Server-side error (AI processing failed, etc.).
Solution:
1. Retry the request once
2. If persistent, try a different image
3. Contact support with the request_id
def diagnose_with_fallback(url, files, data):
"""Handle server errors gracefully."""
response = requests.post(url, files=files, data=data)
if response.status_code == 500:
# Get request ID for support
request_id = response.headers.get("X-Request-ID", "unknown")
print(f"Server error. Request ID: {request_id}")
# Retry once
time.sleep(2)
response = requests.post(url, files=files, data=data)
return response
Analysis Failures (200 OK with error)¶
Sometimes the API returns 200 OK but couldn't complete analysis:
{
"request_id": "...",
"is_plant": false,
"crop_health": "unknown",
"diagnoses": [],
"error": "Unable to analyze: image does not contain a plant"
}
Always check these fields:
response = requests.post(url, files=files, data=data)
result = response.json()
# Check for analysis issues
if result.get("error"):
print(f"Analysis error: {result['error']}")
if not result.get("is_plant"):
print("Image does not contain a plant")
if result.get("image_quality_issue"):
print(f"Quality issue: {result['image_quality_issue']}")
if result.get("crop_health") == "unknown":
print("Could not determine plant health")
Complete Error Handling Example¶
import requests
import time
class TajiriVisionClient:
def __init__(self, base_url="https://api.tajirifarm.com"):
self.base_url = base_url
def diagnose(self, image_path, **kwargs):
"""
Diagnose plant disease with full error handling.
Returns:
dict: Diagnosis result or None on failure
"""
url = f"{self.base_url}/diagnosis/"
# Prepare request
with open(image_path, "rb") as f:
files = {"image": (image_path, f, "image/jpeg")}
data = {
"crop_type": kwargs.get("crop_type"),
"region": kwargs.get("region"),
"language": kwargs.get("language", "en"),
"detail_level": kwargs.get("detail_level", "standard"),
}
# Make request with retry
for attempt in range(3):
try:
response = requests.post(url, files=files, data=data, timeout=60)
except requests.exceptions.Timeout:
print("Request timed out, retrying...")
continue
except requests.exceptions.ConnectionError:
print("Connection error, retrying...")
time.sleep(2)
continue
# Handle response
if response.status_code == 200:
result = response.json()
# Check for soft errors
if result.get("error"):
print(f"Analysis error: {result['error']}")
return None
return result
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 30))
print(f"Rate limited, waiting {retry_after}s...")
time.sleep(retry_after)
elif response.status_code == 400:
print(f"Bad request: {response.json().get('detail')}")
return None
elif response.status_code >= 500:
print(f"Server error, retrying...")
time.sleep(2)
print("Max retries exceeded")
return None
# Usage
client = TajiriVisionClient()
result = client.diagnose(
"plant.jpg",
crop_type="tomato",
region="Kenya",
language="en"
)
if result:
print(f"Diagnosis: {result['diagnoses'][0]['name']}")