Open, ZAR-anchored exchange rates — the open way.
Docs · API · Configuration · Go library · Graph model · Accuracy
openrate is an open-source exchange-rate engine. It ingests rates "the open way" — from central-bank reference files and free public venue feeds, not by reselling a paid API — models every currency as a graph rather than picking a single canonical base, and serves an all-pairs JSON API plus an embedded React UI from a single Go binary.
Part of the Vulos group. The hosted/multi-tenant side is absorbed into Vulos Cloud — see CLOUD.md.
Most rate APIs pick one base currency (usually EUR/USD) and derive everything through it. openrate keeps each source's quotes in their native base (ECB in EUR, SARB in ZAR, …) as edges in a currency graph. Any pair is the product of rates along the shortest path between them, so:
- ZAR is the anchor for free — it's just the default presentation base, a
view over the same graph (
?base=ZAR, or any other). - Directly quoted pairs win — BFS reaches a pair by the fewest hops first, so a direct quote always beats a triangulated cross.
- No single point of contamination — a bad edge only affects paths through it, not every pair.
- Provenance on every number — each rate carries
hops,as_of, andage, so consumers see exactly how stale it is (it matters: fiat is frozen on weekends).
go run ./cmd/openrate # serves :8080, base ZAR, hourly refresh
# or
go build -o openrate ./cmd/openrate && ./openrate -addr :8080 -base ZAR -refresh 1hConfig via flags or env: OPENRATE_ADDR, OPENRATE_BASE, OPENRATE_REFRESH,
OPENRATE_SOURCES, OPENRATE_RATELIMIT. Full reference:
docs/configuration.md.
With Docker:
docker build -t openrate . && docker run -p 8080:8080 openrateInstead of running the binary, import the root package and run the engine
in-process — no subprocess, same store/sources/API/hardening as cmd/openrate:
import "github.com/vul-os/openrate"
local, err := openrate.Start(openrate.Options{}) // ZAR base, hourly refresh, ephemeral port
if err != nil {
log.Fatal(err)
}
defer local.Close()
resp, _ := http.Get(local.APIBaseURL() + "/rates") // or local.BaseURL + "/healthz"Options mirrors the binary's flags (Addr, Base, Refresh, Sources,
RateLimit, ServeUI). Start returns once /healthz is serving. The engine's
building blocks stay under internal/; this package is the supported public API.
| Endpoint | Description |
|---|---|
GET /api/v1/rates?base=ZAR |
All currencies vs base; rate reads "1 base = rate CCY" |
GET /api/v1/convert?from=USD&to=ZAR&amount=100 |
Convert, with rate provenance |
GET /api/v1/meta |
Sources, freshness, currency list |
GET /healthz |
Liveness |
Every rate includes hops, as_of, age_sec, the path and sources, plus a
quality block (grade A–D + confidence) — see below. Full request/response
shapes: docs/api.md.
A separate, flat time-series engine (no currency graph) for central-bank policy
and reference rates worldwide. Enable with -interest-sources (binary) or
Options{Interest: true} (library). Served alongside the FX API:
| Endpoint | Description |
|---|---|
GET /api/v1/interest/rates?area=US&type=policy |
Latest value per series + confidence grade |
GET /api/v1/interest/series?id=us.policy |
One series with full history (timeseries) |
GET /api/v1/interest/meta |
Areas covered, series catalogue, source status |
Out of the box (bis,sarbrates, no keys) this covers 49 central banks' policy
rates with daily history plus the South African ZARONIA family; set
OPENRATE_FRED_API_KEY to auto-enable US benchmark series. Each series carries an
interest-tuned quality grade. See docs/interest-rates.md.
Every price carries a quality assessment so you know how much to trust it:
"quality": {
"grade": "B", "confidence": 0.89,
"freshness": "realtime", "directness": "direct", "source_class": "exchange",
"corroboration": { "sources": 4, "spread_bps": 29, "agree": true },
"caveats": []
}The grade combines freshness (edge age), directness (hop count), source authority (official > exchange > aggregator > unofficial), cross-source agreement (spread in bps), and per-currency caveats (e.g. NGN/EGP/CNY official-vs-parallel-rate flags). Full model: ACCURACY.md. The web UI shows the grade in the converter and a dedicated Accuracy page documenting the methodology.
Selectable with -sources (or OPENRATE_SOURCES). Default: ecb,coinbase,luno,sarb.
| Source | Default | Cadence | Notes |
|---|---|---|---|
| ECB daily file | ✅ | daily | EUR-base, ~30 currencies, ~16:00 CET |
| Coinbase | ✅ | real-time | free/no-auth fiat (incl. ZAR) + crypto — best open intraday source |
| Luno | ✅ | real-time | SA exchange, live BTC/ETH/USDT vs ZAR; bridges to fiat via BTC |
| SARB | ✅ | daily | authoritative ZAR (per USD/GBP/EUR/JPY); slow host → bounded dialer + retries |
| Frankfurter | opt-in | daily | clean JSON ECB mirror |
| open.er-api | opt-in | daily incl. weekends | fills the ECB Fri→Mon gap |
| fawazahmed0 | opt-in | daily | ~400 currencies, dual-CDN, no limits |
| Bank of Canada | opt-in | daily | Valet REST, independent cross-check |
| Yahoo Finance | opt-in | ~1 min | unofficial, ToS-prohibited, rate-limited — last resort |
Because the graph prefers the freshest direct edge, USD→ZAR resolves to the
live Coinbase quote (~seconds old) while EUR/GBP/JPY→ZAR resolve to SARB's
authoritative direct quotes — each chosen automatically, no special-casing.
Add a source by implementing sources.Source and registering it in
internal/sources/registry.go. Full catalog + freshness notes: SOURCES.md.
npm --prefix web install
npm --prefix web run dev # Vite dev server, proxies /api to :8080
npm --prefix web run build # regenerates web/dist, embedded into the binaryopenrate.go public package: embed the engine in-process (Start/Close)
cmd/openrate entrypoint: wires sources -> store -> api + UI
internal/graph currency graph, BFS all-pairs materialization
internal/sources pluggable open sources (ECB live, SARB stub)
internal/store ingest loop + snapshot store
internal/api JSON read endpoints
web Vite + React JSX UI (embedded via go:embed)
Full documentation lives in docs/.
| Guide | What's inside |
|---|---|
| API reference | Every endpoint, params, and full response shapes |
| Configuration | Flags, env vars, and the source spec |
| Go library | Embed the engine in-process with Start/Close |
| Graph model | Why currencies are a graph, not a base |
| Accuracy & quality | The grade/confidence model behind every rate |
| Sources | Full source catalog, cadence, and provenance |
| Web UI | The embedded React dashboard |
MIT — see LICENSE.