Errors¶
Every error response uses the same envelope so clients can implement a single parser.
{
"error": {
"code": "invalid_request",
"message_key": "errors.common.invalid_request",
"message": "Request validation failed.",
"details": { ... },
"request_id": "019e32b5-..."
}
}
| Field | Type | Purpose |
|---|---|---|
code |
string | Stable, machine-readable identifier. Safe to switch on. |
message_key |
string | i18n key. Translate on the client; never display the raw value. |
message |
string | Plain-English fallback. Useful for logs, not for end users. |
details |
object | null | Error-specific structured payload. Schema documented per error. |
request_id |
string | null | Mirrors the X-Request-Id header for correlation with server logs / traces. |
Error catalog¶
4xx — caller errors¶
| HTTP | code |
When |
|---|---|---|
| 400 | invalid_request |
Body or query failed Pydantic validation. details.errors[] has the field-level breakdown. |
| 401 | unauthorized |
Missing or invalid bearer token. details.reason enumerated. |
| 403 | forbidden_scope |
Token verified but missing required scope. details.scope lists what's needed. |
| 404 | not_found |
Object not found, or owned by another tenant. |
| 404 | chart_not_found |
Chart not found, or owned by another tenant. Used by chart-derived endpoints. |
| 409 | idempotency_conflict |
Idempotency-Key was reused with a different request body. |
| 422 | invalid_request |
Same shape as 400; reserved for validation that runs after auth. |
| 422 | unsupported_astrology_system |
A system-specific endpoint received a chart from an unsupported astrology system. |
| 422 | invalid_date_range |
Date range bounds are reversed or exceed the documented endpoint limit. |
| 422 | invalid_datetime_timezone |
A datetime query parameter omitted its timezone offset. |
| 422 | invalid_return_location |
Solar/lunar return relocation supplied only one coordinate. |
| 422 | unsupported_house_system |
house_system was not placidus, whole_sign, or equal. |
| 422 | duplicate_relationship_charts |
Relationship request used the same chart for both sides. |
| 429 | rate_limited |
Tenant rate-limit bucket empty. details.retry_after_seconds provided. |
| 429 | llm_budget_exceeded |
Tenant's rolling LLM cost cap hit. details.cap_usd, details.spent_usd. |
5xx — server / downstream¶
| HTTP | code |
When |
|---|---|---|
| 500 | internal_error |
Unhandled exception. Always paged. |
| 502 | llm_all_providers_failed |
Every LLM provider in the routing chain returned a retryable error. details.attempts[] lists the chain. |
| 502 | panchanga_failed |
Panchanga computation failed for the supplied date, location, or timezone. |
| 502 | muhurta_failed |
Muhurta scoring/search failed for the supplied candidate or window. |
| 502 | focus_areas_failed |
Date-aware Vedic focus-area calculation failed. |
| 503 | llm_unavailable |
A required LLM call could not complete in time. |
| 503 | auth_unavailable |
JWKS endpoint unreachable past cache TTL. |
| 503 | gazetteer_unavailable |
The place-search gazetteer is unavailable. Retry later or let the user enter coordinates manually. |
LLM error codes (forwarded from the router)¶
When an interpretation endpoint surfaces an LLM error the code matches
the domain error class:
| Code | Meaning |
|---|---|
llm_rate_limited |
Upstream provider 429'd. The router already retried fallbacks. |
llm_timeout |
Upstream provider exceeded the per-call timeout. |
llm_unavailable |
Upstream provider returned 5xx or was unreachable. |
llm_context_overflow |
Prompt exceeds model context window. |
llm_policy_error |
The routing policy is missing / malformed for the task. |
llm_budget_exceeded |
Tenant's rolling cost cap reached (also HTTP 429). |
llm_all_providers_failed |
Whole fallback chain exhausted. |
Correlation IDs¶
Every response — error or success — echoes X-Request-Id. Pass that
header in your support tickets; server logs and OTel traces are keyed
on it.
If the client provides X-Request-Id on the inbound request the
server honours that value end-to-end. Otherwise a fresh UUIDv7 is
generated.
Idempotency¶
Mutating endpoints accept an optional Idempotency-Key header. The
first request with a given key is processed and its response cached;
subsequent requests with the same key return the cached response (HTTP
status included).
If a request with the same Idempotency-Key carries a different
body, the server returns HTTP 409 idempotency_conflict rather than
silently overwriting. Keys live for 24 hours.
Use idempotency for any client-driven retry path — most notably
POST /v1/reports and POST /v1/interpretations.