Track central bank rate changes

Policy-rate history arrives oldest first, so you can detect changes by comparing consecutive observations. Compare rate for a point rate, or both bounds for a range.

Do it with the API

  1. Set your API key Set the NARWHAL_API_KEY environment variable in the environment that will run Python. Install the requests library for that interpreter. After public access starts, policy-rate requests are available live on every plan, including Free. Each request consumes one metered unit, including each page of history.
  2. Read the current rate Request /v1/economics/USA/central-bank/policy-rate and /v1/economics/CAN/central-bank/policy-rate. Country codes use uppercase ISO3. The USA response has rate_type: "range", lower_bound and upper_bound. Canada has rate_type: "point", rate and qualifier. Values are decimal strings in percent per annum. The complete script prints both responses so you can inspect their shapes.
  3. Read and paginate history Request /v1/economics/{country}/central-bank/policy-rate/history with start=2024-01-01. Read observations in their returned order, then pass next_cursor as cursor until it is null. Keep the previous value across page boundaries. The script uses params= to encode query values. A cursor pins publications for 24 hours; an expired cursor returns 409.
  4. Compare consecutive values For each point observation, compare rate with the previous point value. For a range, compare the pair lower_bound and upper_bound. Convert strings to decimals before comparison. The first observation establishes the baseline, so the function cannot report whether that observation changed from a value before the requested history window.
  5. Choose the returned date Print effective_on when it is present; otherwise print observed_on. Each response has exactly one of those fields. An optional announced_on describes the announcement date. Keep the returned instrument and rate shape when you store observations, since a history can contain point rates and ranges. The comparison function preserves that distinction in its values.
  6. Run the script daily Save the complete example as track_rates.py in your home directory. Run it once with your key set, then schedule it daily with 0 0 * * * /usr/bin/env python3 "$HOME/track_rates.py". Configure the scheduled environment with your API key and a PATH that finds the interpreter with requests installed. The script prints only changed observations after the two current-response previews.
    Read current responses and detect historical changes
    import json
    import os
    from decimal import Decimal
    
    import requests
    
    BASE_URL = "https://api.narwhalapi.com"
    START = "2024-01-01"
    HEADERS = {
        "Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"
    }
    
    
    def get_json(path, params=None):
        response = requests.get(
            BASE_URL + path,
            headers=HEADERS,
            params=params,
            timeout=10,
        )
        response.raise_for_status()
        return response.json()
    
    
    def rate_value(observation):
        if observation["rate_type"] == "point":
            return Decimal(observation["rate"])
        if observation["rate_type"] == "range":
            return (
                Decimal(observation["lower_bound"]),
                Decimal(observation["upper_bound"]),
            )
        raise ValueError("Unexpected rate_type")
    
    
    def changes(country):
        path = f"/v1/economics/{country}/central-bank/policy-rate/history"
        params = {"start": START}
        previous = None
        result = []
    
        while True:
            page = get_json(path, params=params)
            for observation in page["observations"]:
                current = rate_value(observation)
                if previous is not None and current != previous:
                    date = (
                        observation["effective_on"]
                        if "effective_on" in observation
                        else observation["observed_on"]
                    )
                    result.append((date, previous, current))
                previous = current
    
            cursor = page["next_cursor"]
            if cursor is None:
                return result
            params = {"start": START, "cursor": cursor}
    
    
    if __name__ == "__main__":
        for country in ("USA", "CAN"):
            current = get_json(
                f"/v1/economics/{country}/central-bank/policy-rate"
            )
            print(json.dumps(current, indent=2))
            for date, old, new in changes(country):
                print(country, date, old, new)
    

The function changes(country) returns a list of tuples containing the date, old value and new value. A point value is a decimal; a range is a pair of decimals in lower-bound, upper-bound order. An empty list means no value changes were found between the returned observations.

The saved USA example has a range of 3.50 to 3.75 percent, effective 11 December 2025. The function represents that range as (Decimal("3.50"), Decimal("3.75")). It includes the observation in its results only if the preceding returned value differs. The saved example supplies no previous value, so it cannot establish the size or direction of a change.

What the date means by bank

BankHistory rowsHow to read the date
Federal ReserveRate-change dates from 25 June 2003effective_on is the date the rate took effect.
Reserve Bank of AustraliaChange-date rows from 23 January 1990effective_on is the date the rate took effect.
Czech National BankChange-date rows from 8 December 1995effective_on is the date the rate took effect.
Bank of CanadaObservations from 21 April 2009observed_on is when the source recorded the value.
Bank Negara MalaysiaObservations from 2004observed_on is when the source recorded the value.
Sveriges RiksbankDaily rows from 1 June 1994observed_on is the source observation date.
Norges BankBusiness-day rows from 1 January 1991Use the returned date field for each observation.
Banco Central do BrasilCalendar-day rows from 5 March 1999Dates not yet in force are not served.

Sweden, Norway and Brazil can repeat the same rate across successive rows. Count a change only when the value differs. The USA, Australia and Czechia use change-date rows. For Canada and Malaysia, the observation date can be later than the decision, so an observed change does not establish the decision date.

How often should I check?

The rate only changes at decisions, so a daily check is enough. Each current-rate check is one call per country, plus one call per history page you request. The example reads current rates and history for both countries; those are separate requests.

If you store previous results, compare new observations with the last stored value. Retain that baseline when you narrow the history window, or the first returned observation will have no preceding value to compare.

Does a missing date mean the rate did not change?

A missing date means there is no returned source observation for that date. Narwhal does not invent missing days. A request with date=YYYY-MM-DD returns the latest source observation at or before the requested date; without date, it uses today in UTC.

Future effective dates never replace the current rate early. Preserve the date returned with the value when you record a change. Use policy-rate documentation for the response contract and API errors when a request fails.

Published .

Next steps