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" }}| Field | Type | Notes |
|---|---|---|
code | string | Machine-readable error code. Stable across versions. Branch on this, not on message. |
message | string | Human-readable explanation. Safe to surface to end users. |
status | number | The HTTP status, echoed for clients that find headers awkward. |
details | object | null | Optional 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
| Status | code | Meaning | Retry? |
|---|---|---|---|
| 400 | bad_request | Malformed query string, invalid date range, unknown interval. | No, fix the input. |
| 401 | unauthenticated | Missing, malformed, or revoked API key. | No, fix the key. |
| 403 | forbidden | Authenticated, but key lacks the scope or your subscription is past-due. | No, upgrade or update billing. |
| 404 | not_found | Symbol unknown, or no data for the requested date range. | No, check input. |
| 422 | bad_request | Validation failed (e.g. symbols longer than 8). details contains field errors. | No, fix input. |
| 429 | rate_limit | Burst or monthly limit exceeded. | Yes, with backoff. See rate limits. |
| 500 | internal | Unexpected gateway error. We are paged on these. | Yes, with backoff. |
| 502 | upstream_failure | Every upstream we tried failed for this symbol. | Yes, with backoff. |
| 503 | internal | Maintenance or capacity overload. | Yes, with backoff. |
| 504 | upstream_failure | Upstream 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:
| Code | Retry strategy |
|---|---|
bad_request | Never retry. The input is wrong. Surface to the user. |
unauthenticated | Never retry. Re-mint or fix the key. |
forbidden | Never retry. Surface a billing or scope error. |
not_found | Never retry. The symbol or window is wrong. |
rate_limit | Retry, honoring Retry-After. Cap at 60 seconds and add jitter. |
upstream_failure | Retry up to 3 times with exponential backoff (1s, 2s, 4s). |
internal | Retry 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 httpxfrom 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
- Rate limits — handling
429specifically. - Conventions — JSON casing, dates, currencies.