Documentation · itzsa

NRB Forex

Typed client for Nepal Rastra Bank’s public forex API — fetch, cache, and convert foreign currency → NPR with correct unit handling. No React required.

@itzsa/nrb-forexheadlessNode 18+ / browser

Unofficial

Not affiliated with or endorsed by Nepal Rastra Bank. Rates follow NRB’s publication schedule and API availability. Upstream docs: NRB Forex.

Installation#

Zero runtime dependencies. Uses native fetch (Node 18+).

pnpm add @itzsa/nrb-forex

Quick start#

Module helpers use a default client; create your own for Redis or custom fetch.

import {
  getRate,
  getRatesForDate,
  convert,
  createNrbForexClient,
} from "@itzsa/nrb-forex";

const usd = await getRate("USD"); // today (UTC)
const all = await getRatesForDate("2026-07-17");

// Unit-aware: 100 INR → NPR on buy side
const npr = await convert(100, "INR", "NPR", {
  date: "2026-07-17",
  side: "buy",
});

const client = createNrbForexClient({ fallbackToPreviousDay: true });
await client.getRate("USD", "2026-07-19");

Examples#

Live via the docs proxy. NRB publishes ~22 ISO currencies (not every ISO 4217 code).

Latest rates#

Every currency for the day — date picker, multi-select popover, Buy / Sell / Mid.

Latest FOREX Rates

Nepal Rastra Bank · NPR quotes ·

Currency

No rates match the current filters.

Convert to NPR#

Amount
From
Side

Unit-aware — INR (100) and JPY (10) stay correct automatically.

Rate history#

Pick currency, from, and to — getRateHistory with Buy / Sell.

Currency
From
To

No published days in range.

Published currencies#

All NRB currencies for a date with Unit / Buy / Sell / Mid.

Full NRB list with Buy / Sell ·

Date

Loading…

Package API#

Typed helpers over the NRB V1 rates endpoint.

Client helpers

PropTypeDefaultDescription
getRate(currency, date?, options?) => Promise<ForexRate>Single currency for a day (ISO3, e.g. USD).
getRatesForDate(date?, options?) => Promise<ForexRate[]>All currencies published for one day.
getRateHistory(currency, from, to) => Promise<ForexRate[]>One currency across a date range (charts).
getRatesInRange(from, to) => Promise<DailyForexSnapshot[]>Full snapshots; pagination handled internally.
convert(amount, from, 'NPR', options?) => Promise<number>Foreign → NPR using buy / sell / mid and unit.
getSupportedCurrencies(date?, options?) => Promise<CurrencyInfo[]>ISO3 + name + unit for the day.
createNrbForexClient(options?) => NrbForexClientIsolated client (cache, fetch, retries, fallback).

Convert & units#

ForexRate

PropTypeDefaultDescription
currencystringISO3 code (USD, INR, JPY, …).
currencyNamestringDisplay name from NRB (e.g. U.S. Dollar).
unitnumberNRB quotes buy/sell per this many units (often 1, 10, or 100).
buy / sellnumberAs published — divide by unit for per-1 NPR.
datestringEffective rate date (YYYY-MM-DD).
publishedOnstringWhen NRB published the snapshot.
isFallbackboolean?True when returned via fallbackToPreviousDay.

Math

NPR = amount × (buy|sell|mid) / unit. Prefer convert or perUnitRates.

Caching#

Past NST days forever; today soft-refreshes every 2h for rare midday revisions.

Why refresh still hit NRB before

Browser demos created a new client on each mount, so in-memory cache was empty after reload. The docs proxy now sends Cache-Control / Next revalidate (2h for today, longer for history), and demos reuse one tab-level client.
NRB typically publishes once daily (~09:00–10:00 NST) and may revise
slightly during the business day. This client:

• Past NST dates → cache forever (immutable)
• Today / future → soft TTL of 2 hours (catch revisions, avoid NRB spam)
• Prefer one long-lived client (or Redis) — not a new client per request
• Browser demos: hit a proxy with Cache-Control; do not call NRB from the browser
import {
  createNrbForexClient,
  MemoryForexCache,
  type ForexCache,
} from "@itzsa/nrb-forex";

// Long-lived Node process — in-memory is enough.
// Historical days: cached forever. Today (NST): soft-refresh every 2h
// so rare midday NRB revisions are picked up.
const client = createNrbForexClient({
  cache: new MemoryForexCache(),
  fallbackToPreviousDay: true, // weekends / pre-publish morning
});

await client.getRatesForDate(); // Nepal "today"

// Serverless / multi-instance — use Redis (or similar) so cache is shared.
const redisCache: ForexCache = {
  async get(key) { /* redis.get + JSON.parse */ },
  async set(key, value, ttlMs) { /* redis.set with EX */ },
  async has(key) { /* … */ },
};

const shared = createNrbForexClient({ cache: redisCache });

Errors#

NrbValidationError · NrbApiError · NrbRateNotFoundError — network retries up to 3×.

Mapped from NRB

Missing params or empty ranges surface as typed errors; this client also paginates getRatesInRange for you.

Node / CLI#

Nightly sync helper and npx CLI.

import { syncDailyRates } from "@itzsa/nrb-forex/node";

await syncDailyRates({
  write: async (snapshot) => {
    // upsert snapshot.rates into DB / Redis
  },
});

// CLI
// npx nrb-forex USD 2026-07-17
// npx nrb-forex USD 2026-07-19 --fallback

NRB Forex API (V1)#

Official upstream contract this package wraps. Published typically once per business day.

Base URLhttps://www.nrb.org.np/api/forex/v1/
EndpointGET /rates
Full URLhttps://www.nrb.org.np/api/forex/v1/rates
Empty daysWeekends/holidays may return payload: null — use fallbackToPreviousDay in this client.

GET /rates#

Returns foreign exchange rates for a given date range. All four query parameters are required.

Query parameters

PropTypeDefaultDescription
fromstring (Y-m-d)Starting date. Required.
tostring (Y-m-d)Ending date. Required.
pageintegerCurrent page (cursor). Required.
per_pageinteger 1–100Items per page. Required. Max 100.
GET https://www.nrb.org.np/api/forex/v1/rates?page=1&per_page=100&from=2026-07-17&to=2026-07-17

Unit quirk

Buy/sell are quoted per currency.unit foreign units — not always per 1. INR/KRW are often 100; JPY often 10. Prefer convert so you do not forget to divide.

Response shape#

Top-level envelope, daily payload, and per-currency rate objects.

Root fields

PropTypeDefaultDescription
status{ code: number }HTTP-style status from the API body.
errorsobjectValidation and other errors (present even on some successes).
errors.validationRecord<string, string[]>Field errors for per_page, page, from, to.
paramsobjectEcho of GET parameters (per_page, page, from, to).
dataobjectMain response container.
data.payloadarray | nullArray of daily forex snapshots, or null when empty/invalid.
paginationobjectPage cursor, totals, and prev/next links.

data.payload[] day

PropTypeDefaultDescription
datestring (Y-m-d)FOREX rates for this calendar date.
published_onstringWhen NRB published these rates.
modified_onstringLast modification timestamp for the snapshot.
ratesarrayPer-currency buy/sell quotes for the day.

data.payload[].rates[]

PropTypeDefaultDescription
currency.namestringCurrency display name.
currency.iso3stringISO 4217 alpha-3 code (USD, INR, …).
currency.unitnumberUnits the buy/sell figures are quoted for.
buynumberBuying rate in NPR (for `unit` foreign units).
sellnumberSelling rate in NPR (for `unit` foreign units).

pagination

PropTypeDefaultDescription
pagenumber | nullCurrent page (cursor).
pagesnumber | nullTotal number of pages.
per_pagenumber | nullItems per page.
totalnumber | nullTotal number of items across pages.
links.prevstring | nullURL for the previous page.
links.nextstring | nullURL for the next page.
{
  "status": { "code": 200 },
  "errors": { "validation": null },
  "params": {
    "from": "2026-07-17",
    "to": "2026-07-17",
    "per_page": 100,
    "page": 1
  },
  "data": {
    "payload": [
      {
        "date": "2026-07-17",
        "published_on": "2026-07-17 00:00:17",
        "modified_on": "2026-07-17 00:00:17",
        "rates": [
          {
            "currency": { "iso3": "USD", "name": "U.S. Dollar", "unit": 1 },
            "buy": 133.45,
            "sell": 134.05
          },
          {
            "currency": { "iso3": "INR", "name": "Indian Rupee", "unit": 100 },
            "buy": 160.0,
            "sell": 160.15
          }
        ]
      }
    ]
  },
  "pagination": {
    "page": 1,
    "pages": 1,
    "per_page": 100,
    "total": 1,
    "links": { "prev": null, "next": null }
  }
}

Status & errors#

Body status.code mirrors HTTP-style outcomes. Validation failures return 400 with field messages.

status.code

PropTypeDefaultDescription
200OKRequest succeeded; inspect data.payload for rates.
400Bad RequestInvalid or missing arguments — see errors.validation.
{
  "status": { "code": 400 },
  "errors": {
    "validation": {
      "per_page": [
        "Per Page is required",
        "Per Page must be an integer",
        "Per Page must be at least 1",
        "Per Page must be no more than 100"
      ],
      "page": ["Page is required", "Page must be an integer"],
      "from": [
        "From is required",
        "From must be date with format 'Y-m-d'"
      ],
      "to": ["To is required"]
    }
  },
  "params": { "from": "", "to": "", "per_page": "", "page": "" },
  "data": { "payload": null },
  "pagination": {
    "page": null,
    "pages": null,
    "per_page": null,
    "total": null,
    "links": { "prev": null, "next": null }
  }
}

Browser / CORS

The official NRB host does not send CORS headers. Docs demos call /api/nrb-forex (same query shape). Node and server runtimes can hit NRB directly.