openrate
part of Vulos · open source · the open exchange & interest-rate engine

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.

graph not a base A–D grade per rate 49 central banks MIT licensed
api.v1 · convert
GET /api/v1/convert?from=USD&to=ZAR&amount=100
{
  "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" }
}
ecb sarb luno coinbase · 1 hop EUR GBP JPY USD ZAR anchor
winning path other edges

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=USD and nothing is privileged in storage.
  • Directly quoted pairs win — BFS reaches a pair by the fewest hops first, so a direct hops: 1 quote 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.

  1. 1
    Type an amountDefaults to 100 — edit it and everything recalculates instantly.
  2. 2
    Pick a pairChoose From/To. Try a direct quote (USD → ZAR), then a cross like AUD → BWP and watch it triangulate through ZAR.
  3. 3
    Read the provenanceCheck the grade, path, hops and source before 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 →

GET /api/v1/convert illustrative snapshot
100 USD = ZAR
1 USD = — ZAR
A path hops source age

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.

A
confidence ≥ 0.90

Trust it — fresh, direct, corroborated.

B
confidence ≥ 0.78

Good. A single official quote lands here.

C
confidence ≥ 0.60

Use with care — stale or a triangulated cross.

D
confidence < 0.60

Weak or flagged — a managed or parallel-rate currency.

01

Freshness

Age of the oldest edge on the path. A 4-day window absorbs the weekend gap when fiat markets are shut.

02

Directness

Hop count. A direct 1-hop quote beats a 2+ hop triangulated cross; each hop compounds the spread.

03

Authority

The weakest source on the path — official > exchange > aggregator > unofficial.

04

Corroboration

Independent sources quoting the exact pair, and the basis-point spread between them.

05

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.

Go 1.25+ to build from source or Docker — no Go needed 0 accounts, 0 API keys, 0 external services
# 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
  1. 1
    Install or buildgo run/go build from source, or docker build — one static binary, no runtime deps.
  2. 2
    ConfigureA few flags or env vars (-base, -sources, -refresh). Secure, keyless defaults out of the box.
  3. 3
    Run & verifyOne process serves the JSON API and the embedded UI at /. curl /healthz to confirm it's alive.
  4. 4
    Consume itOpen localhost:8080 in 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.

GET/api/v1/ratesAll currencies vs. a base
GET/api/v1/convertConvert an amount, with provenance
GET/api/v1/metaSources, freshness, currency list
GET/api/v1/interest/ratesCentral-bank policy rates
GET/api/v1/interest/seriesOne rate series, full history
GET/healthzLiveness probe

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.

AreaPolicy rateGradeSource
USUnited States3.625%Bbis
XMEuro area2.150%Abis
GBUnited Kingdom4.000%Abis
ZASouth Africa7.000%Abis · sarbrates
JPJapan0.500%Bbis
+ 44 more areas via one BIS call Interest-rate docs →
49
central banks, policy rates
9+
free open FX feeds
1
static Go binary
$0
keyless, self-hosted, MIT

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.