Error Logging
The Curate-Me API uses a consistent error format across all endpoints. Production errors are tracked in a centralized logging system that supports querying, resolution tracking, and CLI-based management.
Error Response Format
All API errors return a structured JSON response:
{
"error": {
"code": "ANALYSIS_FAILED",
"message": "The vision agent failed to process the uploaded image.",
"details": {
"agent": "vision_agent",
"reason": "Image resolution too low for analysis",
"min_resolution": "256x256"
}
}
}| Field | Type | Description |
|---|---|---|
error.code | string | Machine-readable error code |
error.message | string | Human-readable error description |
error.details | object | Additional context (varies by error type) |
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | Request rate limit has been exceeded |
SUBSCRIPTION_REQUIRED | 403 | A paid subscription is required for this feature |
INVALID_IMAGE_FORMAT | 400 | Uploaded image is not in a supported format (JPEG, PNG, WebP) |
IMAGE_TOO_LARGE | 413 | Uploaded image exceeds the maximum file size (10 MB) |
ANALYSIS_FAILED | 500 | An agent in the analysis pipeline encountered an error |
SESSION_EXPIRED | 401 | The authentication session has expired |
INSUFFICIENT_CREDITS | 403 | Account does not have enough credits for the requested operation |
DUPLICATE_RESOURCE | 409 | A resource with the same identifier already exists |
HTTP Status Codes
| Status | Meaning | Common Causes |
|---|---|---|
400 | Bad Request | Invalid parameters, malformed JSON, missing required fields |
401 | Unauthorized | Missing or expired token, invalid API key |
403 | Forbidden | Insufficient permissions, subscription required, insufficient credits |
404 | Not Found | Resource does not exist or was deleted |
409 | Conflict | Duplicate resource creation |
413 | Payload Too Large | File upload exceeds size limit |
429 | Too Many Requests | Rate limit exceeded, includes Retry-After header |
500 | Internal Server Error | Unexpected server error, agent failure |
502 | Bad Gateway | Upstream LLM provider is unavailable |
503 | Service Unavailable | Scheduled maintenance or temporary overload |
Error Log CLI
The platform includes a CLI tool for querying and managing production errors. This requires an ERROR_LOG_API_KEY configured in your environment.
First-Time Setup
./scripts/errors setup
# Enter your ERROR_LOG_API_KEY when prompted
# Key is stored in ~/.curateme-error-key (not committed to git)View Recent Errors
./scripts/errors recentOutput:
ID | Time | Code | Message
------------|---------------------|--------------------|---------------------------------
err_abc123 | 2026-02-08 14:23:00 | ANALYSIS_FAILED | Vision agent timeout after 30s
err_def456 | 2026-02-08 14:10:00 | RATE_LIMIT_EXCEEDED| Provider rate limit (OpenAI)
err_ghi789 | 2026-02-08 13:55:00 | IMAGE_TOO_LARGE | Upload exceeded 10MB limitGet Error Details
./scripts/errors get err_abc123Returns the full error record including stack trace, request context, and agent execution state.
Get Error Statistics
./scripts/errors summaryOutput:
Error Summary (last 24h)
------------------------
Total errors: 47
Unresolved: 12
Top error codes:
ANALYSIS_FAILED: 18 (38%)
RATE_LIMIT_EXCEEDED: 15 (32%)
IMAGE_TOO_LARGE: 8 (17%)
SESSION_EXPIRED: 6 (13%)Resolve an Error
After fixing the root cause and deploying, mark the error as resolved:
./scripts/errors resolve err_abc123 "Fixed timeout handling in vision agent (PR #142)"Error Handling Best Practices
When integrating with the API, implement error handling that accounts for the following:
- Retry on 429 and 503 — Use the
Retry-Afterheader value for backoff timing. - Do not retry on 400 or 401 — These indicate client-side issues that require changes to the request.
- Handle SSE errors — During streaming responses, listen for
errorevents and handle them gracefully. - Log the error code — Use the
error.codefield for programmatic error handling rather than parsing the message string.
try {
const response = await fetch('/api/v1/analyze', { method: 'POST', ... });
if (!response.ok) {
const { error } = await response.json();
switch (error.code) {
case 'RATE_LIMIT_EXCEEDED':
const retryAfter = response.headers.get('Retry-After');
await delay(retryAfter * 1000);
return retry(request);
case 'SESSION_EXPIRED':
await refreshToken();
return retry(request);
default:
throw new ApiError(error.code, error.message);
}
}
} catch (err) {
// Network or unexpected errors
console.error('API request failed:', err);
}