Skip to main content
Glama
README.md
# ronfix

Romanian leu reference rates, straight from the source.
ronfix is a small keyless client for the National Bank
of Romania (BNR), which publishes its daily reference
fixings as static XML files. It answers the questions
those files support, for any day since 2005: what rate
was in force on a given day, what an amount converts to
or from RON at that rate, how a currency's fixings ran
over a window, and what a year's average comes to the
way ANAF computes it.

Every number is handled in exact decimals. A rate is
kept as the text BNR published (`4.2440`, never
`4.244`), amounts reach the arithmetic as the strings
they were written as, and each result is rounded
exactly once, by the rule you name. Nothing is ever
passed through a float.

## Tools

- `health` — whether BNR answers: the date of its
  latest fixing and how many currencies that fixing
  carries, with a warning when the fixing is more
  than five days old.
- `rate` — the rate in force on a day (default
  today). A weekend, a holiday, or today before the
  ~13:00 Bucharest fixing takes the last fixing
  before it, including December's last fixing in
  early January. The answer carries the rate exactly
  as BNR wrote it, its `multiplier`, and its date as
  `published_on`; today's rate asked before the
  fixing also carries a `note` saying so.
- `convert` — an amount (a decimal string) to or
  from RON at the rate in force on a day. Name the
  pair with `currency` alone (amount in that
  currency, result in RON) or explicitly with
  `from`/`to`. `places` (0 to 8, default 2) and
  `rounding` (`half-up` by default, or `half-even`)
  control the single rounding; `rate_rule` picks
  `in_force` (default) or `last_before` (the last
  fixing strictly before the day). The answer
  carries the rate, its multiplier and date, and a
  `meaning` line, for example: "100 EUR = 509.10 RON
  at BNR's EUR rate 5.0910 RON for 1 EUR, published
  on 2026-10-16 (in force on 2026-10-16), rounded
  half-up to 2 places". Worked check: 10000 HUF at
  1.2354 per 100 is 123.54 RON.
- `rates` — one series per currency (up to 50 named
  via `currencies`, or every currency BNR
  published), each with its multiplier and its
  fixings, oldest first. With `date` (or nothing,
  for today) a series holds the single rate in
  force that day, dated by its fixing; with `from`
  and `to` (default today) it holds every fixing
  in the window, both ends included. A currency
  BNR published but not in the window is listed in
  `absent`; a window with no fixings at all
  answers no series and a note saying so.
- `annual_average` — a year's average as ANAF uses
  it: the mean of BNR's twelve monthly averages
  (each the mean of that month's daily rates), each
  mean rounded once, half up, to four places, with
  the monthly averages and their day counts. A
  year not over yet answers the months so far with
  `complete: false`. Reference points: USD averaged
  4.6885 in 2022 and 4.5743 in 2023; EUR averaged
  4.9315 in 2022.

## Behaviour worth knowing

- Rates are RON per unit, or per 100 units (the
  `multiplier`) for HUF, JPY, KRW and a few others;
  conversions divide or multiply by it accordingly.
- Upstream is BNR's public XML at `curs.bnr.ro`:
  `/nbrfxrates.xml` for the latest fixing and
  `/files/xml/years/nbrfxrates<year>.xml` for a
  whole year. A request is tried up to three times
  on a 5xx, a 429 or a dropped connection. A past
  year's file is fetched once per process; the
  current year's file and the latest fixing are
  kept ten minutes. Dates are Bucharest dates
  whatever the host's timezone.
- Limits: BNR reference rates only, from 2005; no
  cross rates (one side of a conversion is RON);
  windows at most 366 days and 2,000 rates.

## Errors

Failures carry one of four classes at the start of
the message:

- `input` — the question cannot be answered as
  asked and nothing was fetched: a date after
  today or before 2005, a window over 366 days or
  2,000 rates, or a conversion with no RON side.
  The message names which.
- `not_found` — BNR does not publish that currency
  ("BNR publishes no ... rate here; it publishes
  ...", listing the ones it does), or has no rates
  file for that year yet.
- `api` — BNR's server did not answer, even after
  retries.
- `drift` — BNR answered with something that is
  not its rates XML (the old `www.bnr.ro` addresses
  answer HTML with status 200), or a rate that is
  not a plain decimal, or a multiplier change
  inside one month of an annual average. A
  multiplier change between months of an annual
  average answers with a `warning` instead.

## CLI

Each tool is a subcommand taking one JSON argument
string:

```bash
npx @reneturcios/ronfix health '{}'
npx @reneturcios/ronfix rate '{"currency":"EUR","date":"2026-10-16"}'
npx @reneturcios/ronfix convert '{"amount":"10000","currency":"HUF"}'
npx @reneturcios/ronfix rates '{"currencies":["EUR","USD"],"from":"2026-10-01","to":"2026-10-16"}'
npx @reneturcios/ronfix annual_average '{"currency":"USD","year":2022}'
npx @reneturcios/ronfix --version
```

Output is the tool's result as JSON. Exit codes:
`0` on success, `1` on a runtime error, `2` on a
usage error.

## MCP server

```bash
npx -y @reneturcios/ronfix mcp
```

MCP client config:

```json
{
  "mcpServers": {
    "ronfix": {
      "command": "npx",
      "args": ["-y", "@reneturcios/ronfix", "mcp"]
    }
  }
}
```

No key or account is needed; the server reads BNR's
public files only.

## Docker

```bash
docker run -i ghcr.io/cloudcorprecords/ronfix
docker run --rm ghcr.io/cloudcorprecords/ronfix rate '{"currency":"EUR"}'
```

## Development

```bash
npm install
npm run build   # tsc -> dist/
npm test        # tsx --test test/*.test.ts (fake fetch only, no network)
npm run dev     # run the CLI from source
```

## License

MIT — Copyright (c) 2026 Rene Turcios