Skip to content

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.