Errors

Every ODEN API error code and how to handle it: 400 invalid body, 401 unauthorized, 429 rate limit, and 5xx. Includes the error response shape.

Documentation2 min readUpdated 2026-07-30

The ODEN API uses standard HTTP status codes. A non-2xx response indicates that the request was not completed; the JSON response body contains diagnostic details.

Status codes catalog#

HTTP CodeError CodeDescriptionCorrective Action
200okSuccess. Synthesized answer and citations populated.Consume results.
400invalid_requestMalformed JSON body, missing query, or query length over 500 characters.Validate JSON syntax and check query length.
401unauthorizedBearer token missing, expired, or invalid.Check Authorization: Bearer <key> header.
403quota_exceededMonthly account search allocation exhausted.Upgrade plan or purchase a top-up pack in dashboard.
404not_foundEndpoint URL does not exist.Ensure request is dispatched to POST /search.
422unprocessable_entitySemantic parsing failed on search parameters.Inspect request payload types (e.g., depth, answer).
429rate_limit_exceededPer-minute burst limit or concurrency cap reached.Apply exponential backoff with jitter.
500internal_errorUnexpected backend server error.Retry with backoff. Report with trace_id if persistent.
502 / 503gateway_errorTransient Cloudflare edge network or routing blip.Retry automatically after short backoff.

Error response shape#

When an error occurs, the endpoint responds with application/json containing a structured payload:

{
  "error": "invalid_request",
  "message": "query must be 500 characters or fewer",
  "trace_id": "oden-mquycpae-p3thrk"
}
  • error — Machine-readable classification string.
  • message — Human-readable description explaining the exact failure reason.
  • trace_id — Unique distributed trace identifier. Always quote this string when submitting support requests.

Permanent vs transient errors#

Build robust client logic by distinguishing between permanent (client-side) and transient (server-side/network) errors:

  • Permanent errors (400, 401, 403, 404, 422): Deterministic failures that will not succeed on retry. Immediately fail the calling pipeline, log the error, and notify the developer.
  • Transient errors (429, 500, 502, 503, 504): Temporary conditions that typically resolve within seconds. Safe to retry using exponential backoff with jitter.

Production error handler#

Implement a clean Python exception handler that parses ODEN errors and handles retries conditionally:

import time, requests, logging

logger = logging.getLogger("oden_client")

class OdenApiError(Exception):
    def __init__(self, status_code: int, error_code: str, message: str, trace_id: str):
        self.status_code = status_code
        self.error_code = error_code
        self.trace_id = trace_id
        super().__init__(f"ODEN [{status_code} - {error_code}] {message} (trace: {trace_id})")

def execute_search(url: str, headers: dict, payload: dict, max_retries: int = 3) -> dict:
    for attempt in range(max_retries + 1):
        resp = requests.post(url, headers=headers, json=payload, timeout=25)

        if resp.ok:
            return resp.json()

        err_data = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {}
        err_code = err_data.get("error", "unknown_error")
        err_msg = err_data.get("message", resp.text)
        trace_id = err_data.get("trace_id", resp.headers.get("x-trace-id", "unknown"))

        # 1. Fatal client errors: abort immediately
        if resp.status_code in (400, 401, 403, 404, 422):
            logger.error(f"Fatal ODEN error {resp.status_code}: {err_msg} | trace: {trace_id}")
            raise OdenApiError(resp.status_code, err_code, err_msg, trace_id)

        # 2. Transient errors: backoff and retry
        if resp.status_code in (429, 500, 502, 503):
            if attempt == max_retries:
                raise OdenApiError(resp.status_code, err_code, err_msg, trace_id)
            sleep_sec = (2 ** attempt) + 0.5
            logger.warning(f"ODEN transient error {resp.status_code}. Retrying in {sleep_sec}s... | trace: {trace_id}")
            time.sleep(sleep_sec)

    raise RuntimeError("Unexpected retry termination")

Observability & telemetry integration#

To maintain high visibility across production services:

  • Correlate logs: Pass the returned trace_id into your OpenTelemetry spans, Datadog traces, or Sentry error events.
  • Alert on 401 and 403: Set up monitoring alerts for sudden spikes in 401 (auth failure) or 403 (quota depletion) so keys or billing can be addressed before users are impacted.

Next steps#