Repository navigation
OpenExchange
Typed Open Exchange Rates API client for Deno, Bun, Node.js and Cloudflare Workers. Latest and historical currency exchange rates, time series, currency conversion, OHLC data and account usage.
OpenExchange provides validated latest, historical, time-series, conversion, currency, status, and OHLC responses. It uses RESTler for transport and Guardian for runtime response validation.
Your tests don't need to fake HTTP. Have your code take an OpenExchange
instance and pass in a stand-in that returns the shapes from
@tundraconnect/openexchange/schemas or throws a real OpenExchangeError:
import { OpenExchangeError } from '@tundraconnect/openexchange/errors';
const outage = new OpenExchangeError('SERVICE_UNAVAILABLE', { status: 503 });
console.log(outage.code);To exercise the client itself against a fake transport, subclass it and
reassign the protected _fetch.
| Topic | Description |
|---|---|
| API | Client configuration and endpoint methods |
| Errors | Error codes and diagnostic metadata |
| Schemas | Public Guardian schemas and inferred types |
Deno:
deno add jsr:@tundraconnect/openexchangeBun:
bunx jsr add @tundraconnect/openexchangeNode.js:
npx jsr add @tundraconnect/openexchangeimport { OpenExchange } from '@tundraconnect/openexchange';
const client = new OpenExchange({
auth: { type: 'CUSTOM', appId: 'your-app-id' },
});
const rates = await client.getRates({ base: 'USD', symbols: ['EUR'] });
console.log(rates.EUR);0.2.0 is the first release built on the current RESTler/Guardian conventions. The method signatures, return types, and every exported schema and error name are unchanged. What changes:
1. The App ID moves under auth (a compile error until updated):
// 0.1.x
const client = new OpenExchange({ appId: 'your-app-id' });
// 0.2.0
const client = new OpenExchange({
auth: { type: 'CUSTOM', appId: 'your-app-id' },
});2. Some errors now carry a more accurate code. Code that branches on
OpenExchangeError should check these:
| Situation | 0.1.x | 0.2.0 |
|---|---|---|
| HTTP 429 (rate limited) | RESPONSE_ERROR |
RATE_LIMITED, with retryAfterSeconds in context |
| An undocumented 4xx (e.g. 418) |
SERVICE_UNAVAILABLE — or returned as success if the body happened to validate |
UNKNOWN_ERROR |
getHistoricalRates() with a non-YYYY-MM-DD date |
sent to the vendor | rejected locally with INVALID_DATE, no request |
3. Additions (non-breaking): every error exposes a readonly code, so
you can branch on error.code === 'RATE_LIMITED' instead of matching
.message; and passing maxRetryWait at construction makes the client wait
out a rate-limit hint and retry once — see Errors.
MIT. Part of Tundra Connect, typed vendor API clients for Deno, Bun, Node.js and Cloudflare Workers.
- Algolia
- AzureBlob
- CloudflareDNS
- CloudflareEmail
- CloudflareSaaS
- CloudflareTurnstile
- CoinGecko
- Discord
- DodoPayments
- GCS
- GoogleAnalytics
- GoogleCertificateManager
- GoogleWebRisk
- Kalshi
- ntfy
- OpenExchange
- OpenWeatherMap
- PayPal
- Polymarket
- Razorpay
- Resend
- S3
- SendGrid
- Sentry
- Slack
- Stripe
- Telegram
- Twilio
- UpstashRedis
- URLhaus