A lease that escalates rent with CPI compares the index of two reference months, then applies the change within a floor and a cap. Narwhal gives the monthly CPI index of a country, and a short script does the rest.
Get the index for each reference month
A CPI lease clause compares the index of two reference months, usually the same calendar month a year apart. This example uses September and the United Kingdom. One request returns the index for every month from September 2022 to September 2026, and the response below keeps the first and the last.
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
"https://api.narwhalapi.com/v1/economics/GBR/cpi/observations?start=2022-09&end=2026-09&limit=100"{
"country": "GBR",
"as_of": "2026-10-21T00:00:00Z",
"index_base": "2015=100",
"observations": [
{
"period": "2022-09",
"value": "123.8"
},
{
"period": "2026-09",
"value": "144.1"
}
],
"next_cursor": null
}observations lists each period with its value, oldest first. index_base is 2015=100 for the United Kingdom, and it does not matter for a percentage change. as_of is the date the series reflects. The request returns 49 monthly values, so everything fits in one page and next_cursor is null.
Compute the escalation with a floor and a cap
The escalation for a year is the new index divided by the old index, minus one. A floor sets the least the rent may rise, and a cap sets the most. The applied rate is the escalation clamped between the two, and the new rent is the old rent times one plus the applied rate.
import json
import os
import urllib.request
from decimal import Decimal, ROUND_HALF_UP
def get(path):
req = urllib.request.Request(
"https://api.narwhalapi.com" + path,
headers={"Authorization": "Bearer " + os.environ["NARWHAL_API_KEY"]},
)
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
def escalate(rent, cpi_old, cpi_new, floor, cap):
change = cpi_new / cpi_old - 1
applied = min(max(change, floor), cap)
return (rent * (1 + applied)).quantize(Decimal("0.01"), ROUND_HALF_UP), change, applied
data = get("/v1/economics/GBR/cpi/observations?start=2022-09&end=2026-09&limit=100")
cpi = {o["period"]: Decimal(o["value"]) for o in data["observations"]}
rent, floor, cap = Decimal("2000.00"), Decimal("0.02"), Decimal("0.05")
for year in (2023, 2024, 2025, 2026):
old, new = cpi[f"{year - 1}-09"], cpi[f"{year}-09"]
rent, change, applied = escalate(rent, old, new, floor, cap)
print(f"{year}: CPI {old} -> {new}, change {change:.2%}, applied {applied:.2%}, rent {rent}")
The lease in this example starts at 2,000.00 a month with a floor of 2 percent and a cap of 5 percent. Each anniversary compares September with the previous September. The script prints one line per year, such as 2023: CPI 123.8 -> 132.0, change 6.62%, applied 5.00%, rent 2100.00. It works in Decimal throughout and rounds the rent to cents once per year, so a lease that rounds the rate first, or rounds the rent differently, will differ by a few cents.
| Year | Index change | Applied rate | Rent |
|---|---|---|---|
| 2023 | 123.8 to 132.0, 6.62% | 5.00% (capped) | 2,100.00 |
| 2024 | 132.0 to 134.2, 1.67% | 2.00% (floored) | 2,142.00 |
| 2025 | 134.2 to 139.3, 3.80% | 3.80% | 2,223.40 |
| 2026 | 139.3 to 144.1, 3.45% | 3.45% | 2,300.01 |
Check the first year by hand. 132.0 divided by 123.8 is 1.0662, so the index rose 6.62 percent. That is above the cap, so the applied rate is 5 percent, and 2,000.00 times 1.05 is 2,100.00. For 2024, 134.2 divided by 132.0 is 1.0167, which is below the 2 percent floor, so the rent rises by 2 percent to 2,142.00.
The cap bites in 2023, the floor lifts 2024, and the two later years follow the index. The rent compounds, because each year starts from the rent of the year before. A lease that applies each rate to the starting rent, without compounding, needs the starting rent kept in a separate variable.
What the API gives and what the lease sets
| Item | Source |
|---|---|
| Index value of each month | Narwhal CPI observations |
| Which index the lease names | The lease |
| Reference months | The lease |
| Floor and cap | The lease |
| Rounding and compounding | The lease |
Narwhal gives the headline consumer price index of a country. If the contract names another index, such as a core index, a regional index or an index from a different publisher, the figures here may not be the ones the contract uses. Read the clause before you run the script.
Which month's index should the escalation use?
The months the lease names. The example uses September because the clause here compares September with the previous September, and the latest index for the United Kingdom is for September 2026. Request the two months with start and end, or read one past month with /v1/economics/{country}/cpi?period=YYYY-MM.
What if the new month is not published at the anniversary?
A request for a month without a published value returns a 404 problem response with the code series_not_found. The CPI endpoint without a period returns the latest month and its released_on date. If the anniversary comes before the reference month is out, leave the rent unchanged and run the script again when the month appears, or follow the fallback the lease names.
What happens if prices fall?
The change is negative, and the clamp decides the result. With a floor of 0 percent the applied rate is 0 percent and the rent stays the same. With a floor of 2 percent, as above, the rent still rises by 2 percent. A lease without a floor lets the rent fall by the percentage change, and the same function handles that case with a floor of -1.
Can I run the same check for another country?
Change the country code in the path. The endpoint returns the headline index of the country, so the clause must name that index. The start and end parameters accept YYYY, YYYY-MM or YYYY-QN, and the shape of the period strings in the response tells you whether the series is monthly or quarterly. The how-to guide on calculating CPI year over year covers the percentage change in more detail.
Published .