Skip to content

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

oneapi.js
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