On 30 September 2026 one US dollar bought 17,891.1 Indonesian rupiah. Ask the observations endpoint for the base currency, the quote currency and the date, and read the rate from that day's row.
Get the dated rate with the API
- Choose the currencies and date Put the base currency in the observations path and the quote currency in
currencies; for USD to IDR, useUSDin the path andIDRin the parameter. Set bothstart_dateandend_dateto2026-09-30to ask for the observation on 30 September 2026, with the same date at each end of the range. - Set your API key Get a free key and keep it in the environment variable
NARWHAL_API_KEY. The script reads that variable for theAuthorization: Bearer YOUR_KEYheader, so put your key in the environment before making the request. - Request the observation Use the observations endpoint with the date parameters and authorization header shown here. The script reads the response's
observationsarray and prints each returned day's date alongside itsratesarray, where the quote currency and its rate appear together.Python, USD to IDR on a past dateimport os import requests r = requests.get("https://api.narwhalapi.com/v1/fx/rates/USD/observations", params={"currencies": "IDR", "start_date": "2026-09-30", "end_date": "2026-09-30"}, headers={"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"}, timeout=10) r.raise_for_status() for day in r.json()["observations"]: print(day["date"], day["rates"]) - Read the returned date and rate For 30 September 2026, the output is
2026-09-30 [{'currency': 'IDR', 'rate': '17891.1'}]; use the date on the returned row when assigning the rate to a date in your own records. For a list of dates, request a start and an end, then uselimitandnext_cursorto paginate through the observations, since a page can hold many days.
Use GET /v1/fx/rates/{base}/observations for a past date. The endpoint for a current rate is GET /v1/fx/rates/{base}?currencies=...; the dated request belongs on the observations path with its start and end parameters.
The response contains base, observations and next_cursor. Inside observations, each row has a date and a rates array; each rate has currency and rate fields. When extracting the value for a sheet, take the date from the observation and the value from its IDR entry, preserving the connection between the requested quote currency and the returned rate.
If the request returns an error, its body uses application/problem+json with status, code, detail and request_id. Read the error's code and detail before treating the response as observations. An error body has these error fields; the dated rates belong in a successful response's observations array.
Find a historical rate without code
For a date you want to look up by hand, use the Narwhal currency pages, which show historical rates. Find the currencies and the date you need, and keep the date with the rate when you copy it into a sheet. A dated value lets you refer back to the observation you used for the conversion.
For a list, paste the dates and rates the script writes into a sheet, with dates in the lookup column and rates in the following column. Use date for the date you want and table for those columns in the formula. Replace those placeholders with your sheet's date reference and lookup range. The final FALSE selects an exact date match, while 2 selects the rate column. Keep the currency pair with the table so you know which base and quote the pasted values belong to, and use the returned observation dates as the dates in that lookup column.
=VLOOKUP(date, table, 2, FALSE)Which option to use when
| Situation | Best option | Why |
|---|---|---|
| A single date by hand | The currency page | The currency pages show historical rates |
| A list of dates | The observations endpoint | Request a start and an end, then look up each date |
| A single date outside your plan window | A Basic or Pro key | Use a plan whose history window includes the date |
History access depends on your plan's window. An earlier start date is moved up to the window's first day without an error, so read the first returned date; 403 plan_upgrade_required applies when the whole range ends before the window, or when a single date or period is outside it.
What if the date was a weekend or a holiday?
If the requested date has no rate, take the last earlier date with a row. Rates are daily observations, and weekends or other days without a rate have no row, so a request for a Saturday returns no observation for that date.
Use the date on that earlier row when recording which observation supplied the rate. The invoice-date method explains the fallback for a date without an observation.
What does the exchange rate mean?
The rate is the number of quote-currency units per unit of the base currency. In the 30 September 2026 USD/IDR observation, USD is the base and IDR is the quote, so 17,891.1 means Indonesian rupiah per US dollar. Multiply the USD amount by that dated rate to get its IDR value.
How should I handle decimal rates?
Keep the returned rates as decimals or strings when you do money maths. The API returns decimal strings, as the quoted '17891.1' in the 30 September 2026 output illustrates. Use a decimal representation for the multiplication in your calculation rather than converting the rate to a binary float.
Worked example: USD/IDR on 30 September 2026
To convert 1,000 USD using the 30 September 2026 observation, first read the IDR rate from that date's USD row. Then multiply the dollar amount by 17,891.1 IDR per USD. Keep the date attached to the result so the amount refers to the historical observation used in the calculation. In the sheet, look up 30 September 2026 in the pasted dates, take its rate, and use that value as the multiplier for the USD amount.
| Step | Value |
|---|---|
| Observation date | 30 September 2026 |
| Amount in the base currency | 1,000 USD |
| Rate on 30 September 2026 | 17,891.1 IDR per USD |
| Multiply | 1,000 USD × 17,891.1 IDR per USD |
| Amount in the quote currency | 17,891,100 IDR |
Published .
