Documentation
Bulk Conversion and Concurrency
There is no bulk endpoint. Batch with /v1/convert, fan out /v1/history one currency at a time, and pace a bounded worker pool.
There is no bulk endpoint, no batch job resource, and no way to post a list of work. Every data endpoint is a GET, so throughput comes from making fewer, better-shaped requests and from controlling concurrency yourself. Start by checking whether you need a fan-out at all.
/v1/rates/{base} returns the current snapshot for every currency in one request. That is the cheap path for a current 100-currency display. A 100-currency snapshot is still not automatically one request: 100 historical series through /v1/history cost 100 requests because history takes one currency per call, and 100 arbitrary conversions cost four calls through /v1/convert, which accepts up to 25 targets in one to list. The request count follows the shape of the data, not the number of rows you ultimately store.
// Small p-limit style pool with no dependency.
function createLimiter(max: number) {
let active = 0;
const waiting: Array<() => void> = [];
const release = () => {
active--;
const resume = waiting.shift();
if (resume) { active++; resume(); }
};
return async function run<T>(task: () => Promise<T>): Promise<T> {
if (active >= max) await new Promise<void>((r) => waiting.push(r));
else active++;
try { return await task(); }
finally { release(); }
};
}
const run = createLimiter(4);
async function historyFor(currencies: string[], key: string) {
return Promise.all(
currencies.map((currency) =>
run(async () => {
const url = new URL("https://api.currency-core.com/v1/history");
url.searchParams.set("currency", currency);
url.searchParams.set("from", "2026-10-01");
url.searchParams.set("to", "2026-11-01");
const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
if (!res.ok) throw new Error(`${currency}: ${res.status}`);
return res.json();
}),
),
);
}
Concurrency bounds in-flight requests. It does not bound requests per minute, and the per-minute limit counts every attempt, including errors and retries. Size the pool from your plan: Starter allows 120 per minute, so two requests per second. If each call takes 1 second, four workers can theoretically issue four per second and will spend the minute budget in 30 seconds. Use fewer workers, add a start-time gap, or use the rate limit to compute a safe pool.
History fan-out adds up. Refreshing 100 series once a day for 30 days is 3,000 requests, comfortably inside Starter’s 50,000 monthly quota. Refreshing the same 100 series hourly is 72,000 requests, which is over that quota. Conversion batching is the opposite: 100 targets as four /convert calls saves 96 requests compared with one call per target. Cache historical results and reuse them; history for a past date does not need refetching.