Open, ZAR-anchored
exchange & interest rates.
openrate ingests rates the open way — from central-bank reference files and free public venue feeds, not by reselling a paid API. It models every currency as a graph rather than a single base, grades every number, and serves an all-pairs FX API plus a 49-central-bank interest-rate engine from one Go binary.
{
"from": "USD", "to": "ZAR", "amount": 100,
"result": 1843.20,
"rate": { "value": 18.432, "hops": 1, "age_sec": 7 },
"path": ["USD", "ZAR"],
"sources": ["coinbase"],
"quality": { "grade": A, "confidence": 0.94,
"freshness": "realtime", "directness": "direct" }
}
the model
A graph, not a single base.
Most rate APIs pick one base — usually EUR or USD — and derive everything through it. openrate keeps each source's quotes in their native base as edges in a currency graph, and resolves any pair by breadth-first search along the shortest path.
- ZAR is the anchor for free — just the default presentation base, a view over the same graph. Ask for
?base=USDand nothing is privileged in storage. - Directly quoted pairs win — BFS reaches a pair by the fewest hops first, so a direct
hops: 1quote always beats a triangulated cross. - No single point of contamination — a bad edge only affects paths through it, not every pair in the system.
getting started
Try it in 3 steps: convert any pair.
The widget on the right is a live, client-side replay of what GET /api/v1/convert returns
from a running instance. Follow the numbered steps below — they're annotated on the widget too.
- 1Type an amountDefaults to
100— edit it and everything recalculates instantly. - 2Pick a pairChoose
From/To. Try a direct quote (USD → ZAR), then a cross like AUD → BWP and watch it triangulate through ZAR. - 3Read the provenanceCheck the
grade,path,hopsandsourcebefore you trust the number — a direct 1-hop quote always grades higher than a cross.
Full walkthrough — the web UI, the raw API, the interest-rate engine — in Getting started →
how much to trust it
A grade on every number.
A rate is only as trustworthy as its provenance, so openrate makes it explicit: a letter grade and a 0–1 confidence attached to every rate, computed from five factors.
Trust it — fresh, direct, corroborated.
Good. A single official quote lands here.
Use with care — stale or a triangulated cross.
Weak or flagged — a managed or parallel-rate currency.
Freshness
Age of the oldest edge on the path. A 4-day window absorbs the weekend gap when fiat markets are shut.
Directness
Hop count. A direct 1-hop quote beats a 2+ hop triangulated cross; each hop compounds the spread.
Authority
The weakest source on the path — official > exchange > aggregator > unofficial.
Corroboration
Independent sources quoting the exact pair, and the basis-point spread between them.
Caveats
Per-currency notes — official-vs-parallel (NGN, EGP), managed regimes (CNY) — each with a human-readable reason.
the engine
Everything in one binary.
A graph, not a base
Every source keeps its quotes in their native base as edges in a currency graph. Any pair is the product of rates along the shortest path — a direct quote always beats a triangulated cross.
Open, ZAR-anchored sourcing
Ingests central-bank files and free public feeds — ECB, SARB, Coinbase, Luno and more — never reselling a paid API. ZAR is the anchor for free: just the default view over the graph.
Quality on every number
Each rate carries a grade A–D plus confidence, combining freshness, directness, source authority, cross-source agreement, and per-currency caveats. Provenance is never hidden.
All-pairs JSON API
GET /api/v1/rates, /convert and /meta. Every response includes hops, as_of, age, the resolution path, and the contributing sources — read-only, CORS-friendly, rate-limited.
Interest-rate engine
A flat time-series engine covering 49 central banks' policy rates with daily history, plus the South African ZARONIA family — served alongside the FX API under /interest.
One binary — or a Go library
Serve the API and embedded UI from a single static binary, or import github.com/vul-os/openrate and run the same engine in-process. Same store, sources, and hardening.
install · self-host
Up and running in a couple of commands.
No accounts, no API keys, no external services. One process serves the JSON API and the embedded UI. Run it, build it, ship it in Docker, or embed the engine in your own Go program.
# clone, then serve :8080, base ZAR, hourly refresh, all free sources
git clone https://github.com/vul-os/openrate.git && cd openrate
go run ./cmd/openrate
# or build a single static binary
go build -o openrate ./cmd/openrate
./openrate -addr :8080 -base ZAR -refresh 1h
# one image, no external services, non-root user
docker build -t openrate .
docker run -p 8080:8080 openrate
# consume it
curl 'localhost:8080/api/v1/convert?from=USD&to=ZAR&amount=100'
import "github.com/vul-os/openrate"
# in-process — same store, sources, API, hardening
local, err := openrate.Start(openrate.Options{})
if err != nil { log.Fatal(err) }
defer local.Close()
resp, _ := http.Get(local.APIBaseURL() + "/rates")
# confirm it's alive
curl localhost:8080/healthz
# then open the embedded web UI
open http://localhost:8080
- 1Install or build
go run/go buildfrom source, ordocker build— one static binary, no runtime deps. - 2ConfigureA few flags or env vars (
-base,-sources,-refresh). Secure, keyless defaults out of the box. - 3Run & verifyOne process serves the JSON API and the embedded UI at
/.curl /healthzto confirm it's alive. - 4Consume itOpen
localhost:8080in a browser, or curl the API directly — see Getting started.
Illustrative — the embedded web UI served from / once the binary is running. Real numbers come from the live sources you configured.
the API
A small, read-only JSON surface.
Every response carries provenance: hops, as_of, age_sec, the
resolution path, and the contributing sources.
the second engine
Interest rates, not just FX.
A separate flat time-series engine runs alongside the currency graph under /api/v1/interest.
Out of the box — no keys — it covers 49 central banks' policy rates with daily history via BIS,
plus the South African ZARONIA family. Rates don't triangulate, so each series is independent and
carries its own interest-tuned grade.
Self-host free. Forever.
One static binary, open feeds, no keys — rates you can actually see the provenance of. Clone it, run it, and point your app at the JSON API.