Skip to content

Errors

Every non-2xx response from oneapi.finance returns the same JSON envelope. The shape is deliberately small so error handling can be uniform across endpoints.

Error envelope

{
"code": "not_found",
"message": "Symbol 'XYZQ' not found in any active universe.",
"status": 404,
"details": {
"symbol": "XYZQ"
}
}
FieldTypeNotes
codestringMachine-readable error code. Stable across versions. Branch on this, not on message.
messagestringHuman-readable explanation. Safe to surface to end users.
statusnumberThe HTTP status, echoed for clients that find headers awkward.
detailsobject | nullOptional structured context. Schema varies by code.

The pydantic model behind this envelope lives at oneapi_core.responses.errors.ErrorResponse. The extra="forbid" config means we will never silently add a top-level field; new context lands in details.

HTTP status reference

StatuscodeMeaningRetry?
400bad_requestMalformed query string, invalid date range, unknown interval.No, fix the input.
401unauthenticatedMissing, malformed, or revoked API key.No, fix the key.
403forbiddenAuthenticated, but key lacks the scope or your subscription is past-due.No, upgrade or update billing.
404not_foundSymbol unknown, or no data for the requested date range.No, check input.
422bad_requestValidation failed (e.g. symbols longer than 8). details contains field errors.No, fix input.
429rate_limitBurst or monthly limit exceeded.Yes, with backoff. See rate limits.
500internalUnexpected gateway error. We are paged on these.Yes, with backoff.
502upstream_failureEvery upstream we tried failed for this symbol.Yes, with backoff.
503internalMaintenance or capacity overload.Yes, with backoff.
504upstream_failureUpstream timeout.Yes, with backoff.

details payloads documented per code:

  • unauthenticated{ "reason": "missing_header" | "invalid_format" | "unknown_key" | "revoked" }.
  • forbidden{ "reason": "insufficient_scope" | "subscription_past_due" | "ip_not_allowlisted" }.
  • not_found{ "symbol": "..." } or { "from": "...", "to": "..." } for time-series gaps.
  • bad_request{ "field": "...", "message": "...", "value": ... } for each invalid field.
  • rate_limit{ "scope": "minute" | "month", "retry_after_seconds": number }.
  • upstream_failure{ "sources_tried": ["yahoo_query1", "stockanalysis"], "last_error": "..." }.

Retry guidance

Apply this matrix in client code:

CodeRetry strategy
bad_requestNever retry. The input is wrong. Surface to the user.
unauthenticatedNever retry. Re-mint or fix the key.
forbiddenNever retry. Surface a billing or scope error.
not_foundNever retry. The symbol or window is wrong.
rate_limitRetry, honoring Retry-After. Cap at 60 seconds and add jitter.
upstream_failureRetry up to 3 times with exponential backoff (1s, 2s, 4s).
internalRetry up to 3 times with exponential backoff (1s, 2s, 4s).

Idempotent endpoints (every GET /v1/*) are safe to retry without uniqueness guards.

A complete error handler

import httpx
from typing import Any
class OneApiError(Exception):
def __init__(self, code: str, message: str, status: int, details: dict[str, Any] | None):
super().__init__(f"{code}: {message}")
self.code = code
self.message = message
self.status = status
self.details = details or {}
def call(method: str, path: str, **kwargs):
r = httpx.request(method, f"https://api.oneapi.finance{path}", **kwargs)
if 200 <= r.status_code < 300:
return r.json()
try:
body = r.json()
except ValueError:
body = {"code": "internal", "message": r.text or "Unknown error",
"status": r.status_code, "details": None}
raise OneApiError(
code=body.get("code", "internal"),
message=body.get("message", "Unknown error"),
status=body.get("status", r.status_code),
details=body.get("details"),
)
class OneApiError extends Error {
constructor({ code, message, status, details }) {
super(`${code}: ${message}`);
this.code = code;
this.status = status;
this.details = details ?? {};
}
}
async function call(path, options = {}) {
const r = await fetch(`https://api.oneapi.finance${path}`, options);
if (r.ok) return r.json();
let body;
try {
body = await r.json();
} catch {
body = { code: "internal", message: r.statusText, status: r.status };
}
throw new OneApiError(body);
}

What’s next