JavaScript client
A small, dependency-free JavaScript client for oneapi.finance. Works in any
runtime with a global fetch: Node 18+, Deno, Bun, browsers.
Install
There is no npm package. Copy this file into your project.
Client
const DEFAULT_BASE = "https://api.oneapi.finance";const DEFAULT_TIMEOUT = 15_000;
export class OneApiError extends Error { constructor({ code, message, status, details }) { super(`${code}: ${message}`); this.name = "OneApiError"; this.code = code; this.status = status; this.details = details ?? {}; }}
export class OneApi { constructor({ apiKey, base = DEFAULT_BASE, timeout = DEFAULT_TIMEOUT } = {}) { this.apiKey = apiKey ?? process.env.ONEAPI_KEY; this.base = base; this.timeout = timeout; if (!this.apiKey) throw new Error("ONEAPI_KEY not set"); }
async #get(path, params = {}) { const url = new URL(this.base + path); for (const [k, v] of Object.entries(params)) { if (v !== undefined && v !== null) url.searchParams.set(k, String(v)); } const ac = new AbortController(); const t = setTimeout(() => ac.abort(), this.timeout); let r; try { r = await fetch(url, { headers: { Authorization: `Bearer ${this.apiKey}` }, signal: ac.signal, }); } finally { clearTimeout(t); } 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); }
quote({ symbol, symbols, exchange } = {}) { return this.#get("/v1/quote", { symbol, symbols: symbols?.join(","), exchange }); } timeSeries({ symbol, interval = "1day", outputsize = 30, start_date, end_date } = {}) { return this.#get("/v1/time_series", { symbol, interval, outputsize, start_date, end_date }); } statistics(symbol) { return this.#get("/v1/statistics", { symbol }); } profile(symbol) { return this.#get("/v1/profile", { symbol }); } dividends(symbols) { return this.#get("/v1/dividends", { symbols: symbols.join(",") }); } splits(symbol) { return this.#get("/v1/splits", { symbol }); } symbolSearch(query, limit = 10) { return this.#get("/v1/symbol_search", { query, limit }); } fx(pair, outputsize = 1) { return this.#get("/v1/fx/time_series", { pair, outputsize }); } usage() { return this.#get("/v1/usage"); }}Usage
import { OneApi, OneApiError } from "./oneapi.js";
const api = new OneApi();
try { const { quote } = await api.quote({ symbol: "AAPL" }); console.log(`AAPL price: ${quote.price} ${quote.currency}`);
const { values } = await api.timeSeries({ symbol: "AAPL", outputsize: 30 }); for (const bar of values) console.log(bar.datetime, bar.close);} catch (e) { if (e instanceof OneApiError) { if (e.code === "rate_limit") console.error("Slow down."); else if (e.code === "not_found") console.error("Unknown symbol."); else throw e; } else { throw e; }}Retry helper
async function withRetry(fn, { attempts = 5, baseMs = 1000, maxMs = 60_000 } = {}) { let backoff = baseMs; for (let i = 0; i < attempts; i++) { try { return await fn(); } catch (e) { const retryable = e instanceof OneApiError && [429, 500, 502, 503, 504].includes(e.status); if (!retryable || i === attempts - 1) throw e; const retryAfter = Number(e.details?.retry_after_seconds) * 1000 || backoff; const wait = Math.min(retryAfter, maxMs) + Math.random() * 500; await new Promise((r) => setTimeout(r, wait)); backoff = Math.min(backoff * 2, maxMs); } }}
const { quote } = await withRetry(() => api.quote({ symbol: "AAPL" }));Browser usage
The same client works in a browser if you proxy through your own backend. Do
not ship a live key to the browser. Instead, expose your own
/api/quote/:symbol route on your server, call OneApi from there, and have
the browser hit your backend.
See also
- TypeScript types — strict types for every response.
- Errors
- Rate limits