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 in the OpenAI-compatible envelope:
{
"error": {
"message": "Estimated cost $3.50 exceeds per-request limit $2.00",
"type": "permission_error",
"param": null,
"code": "cost_limit"
}
}| Field | Type | Description |
|---|---|---|
error.message | string | Human-readable error description |
error.type | string | Error category (e.g. permission_error, rate_limit_error, authentication_error) |
error.param | string or null | The request parameter that caused the error, if applicable |
error.code | string | Machine-readable error code for programmatic handling |
For the full type/code catalog, see the Gateway Error Reference.
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
invalid_api_key | 401 | Missing or invalid Curate-Me API key |
missing_provider_key | 401 | No provider API key supplied and no stored secret exists |
key_rotated | 401 | The API key has been rotated; use the replacement key |
invalid_json | 400 | Request body is not valid JSON |
missing_model | 400 | The model field is missing from the request body |
unknown_model | 400 | The model is not recognized by the gateway |
provider_mismatch | 400 | The model does not belong to the provider endpoint it was sent to |
model_retired | 400 | The requested model has been retired |
cost_limit | 403 | Estimated cost exceeds the per-request cap |
daily_budget | 403 | The organization’s daily budget has been exhausted |
monthly_budget | 403 | The organization’s monthly budget has been exhausted |
pii_detected | 403 | PII or secrets were detected in the request content |
model_not_allowed | 403 | The model is not in the organization’s allowlist |
needs_approval | 403 / 202 | The request requires human (HITL) approval before execution |
request_too_large | 413 | Request body exceeds the tier’s body-size limit |
rate_limit | 429 | The requests-per-minute (RPM) limit has been exceeded |
plan_limit_exceeded | 429 | The plan’s daily request quota or budget has been exhausted |
internal_error | 500 | Unexpected error within the gateway |
connection_error | 502 | Failed to connect to the upstream provider |
circuit_breaker_open | 503 | The provider’s circuit breaker is open after repeated failures |
timeout | 504 | The upstream provider timed out |
HTTP Status Codes
| Status | Meaning | Common Causes |
|---|---|---|
400 | Bad Request | Invalid JSON, missing fields, provider mismatch, retired model |
401 | Unauthorized | Missing or invalid API key or access token |
403 | Forbidden | Blocked by governance (cost, budget, PII, model allowlist, HITL) |
404 | Not Found | Resource does not exist or was deleted |
409 | Conflict | Duplicate resource creation |
410 | Gone | Endpoint or model has been retired |
413 | Payload Too Large | Request body exceeds the tier body-size limit |
429 | Too Many Requests | Rate limit exceeded or plan quota exhausted; includes Retry-After |
500 | Internal Server Error | Unexpected gateway error |
502 | Bad Gateway | Upstream LLM provider is unavailable |
503 | Service Unavailable | Circuit breaker open or scheduled maintenance |
504 | Gateway Timeout | Upstream provider timed out |
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 | rate_limit | RPM limit exceeded (OpenAI)
err_def456 | 2026-02-08 14:10:00 | daily_budget | Daily budget exhausted
err_ghi789 | 2026-02-08 13:55:00 | pii_detected | Credit card number in request bodyGet Error Details
./scripts/errors get err_abc123Returns the full error record including stack trace, request context, and governance decision state.
Get Error Statistics
./scripts/errors summaryOutput:
Error Summary (last 24h)
------------------------
Total errors: 47
Unresolved: 12
Top error codes:
rate_limit: 18 (38%)
daily_budget: 15 (32%)
pii_detected: 8 (17%)
upstream_502: 6 (13%)Resolve an Error
After fixing the root cause and deploying, mark the error as resolved:
./scripts/errors resolve err_abc123 "Raised the org's RPM limit (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.
// Pattern shown against the gateway proxy endpoint — the same shape works
// for the admin REST surface.
try {
const response = await fetch(
'https://api.curate-me.ai/v1/openai/chat/completions',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CM-API-Key': process.env.CURATE_ME_API_KEY,
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Hello' }],
}),
},
);
if (!response.ok) {
const { error } = await response.json();
switch (error.code) {
case 'rate_limit':
const retryAfter = response.headers.get('Retry-After');
await delay(Number(retryAfter) * 1000);
return retry(request);
case 'invalid_api_key':
await refreshKey();
return retry(request);
default:
throw new ApiError(error.code, error.message);
}
}
} catch (err) {
// Network or unexpected errors
console.error('API request failed:', err);
}