Skip to content

rates.json schema

rates.json is a precomputed snapshot of every IGCP-published monthly base rate and every cohort-anchored annual rate, generated by scripts/build-rates-json.ts and published to GitHub Pages. Python, Java, Excel, and spreadsheet users can fetch it once and reproduce IGCP’s numbers without porting any code.

  • Latest: https://primor.github.io/igcp-aforro/rates.json
  • Per-release snapshot: https://primor.github.io/igcp-aforro/v/<calver>/rates.json (e.g. v/2026.420.0/rates.json)

The file is regenerated:

  • after every release (the workflow runs pnpm build:rates-json and deploys the snapshot to Pages);
  • after every Euribor / IGCP base-rate refresh PR is merged via data-refresh.yml.
{
"schemaVersion": 1,
"generatedAt": "2026-04-20T08:00:00Z",
"libraryVersion": "2026.420.0",
"euriborSourceMeta": {
"lastRefreshedAt": "2026-04-19T07:42:11Z",
"source": "Deutsche Bundesbank time-series API",
"sourceUrl": "https://api.statistiken.bundesbank.de/rest/download/BBIG1/...",
"seriesId": "BBIG1.D.D0.EUR.MMKT.EURIBOR.M03.BID._Z"
},
"series": {
"E": { "metadata": { "...": "..." }, "monthlyBaseRates": [], "cohortRates": [] },
"F": { "metadata": { "...": "..." }, "monthlyBaseRates": [], "cohortRates": [] }
}
}

series.E and series.F carry the same shape and are populated independently — Série E rows start in November 2017 (the first month with a clean ≥10-business-day Euribor 3M history under the IGCP averaging rule), Série F rows start in June 2023.

schemaVersion is bumped whenever a backwards-incompatible change ships, so consumers can pin or assert. Adding a new series under series.<code> is not considered a breaking change.

The static SeriesMetadata for the series — maturityYears, subscriptionStartDate, subscriptionEndDate (only present for closed series like Série E), minUnits, maxUnits, baseRateClampMinPct, baseRateClampMaxPct, baseRateSpreadPct (Série F: '0', Série E: '1'), defaultIrsRate, the full premiumTiers list, etc. Identical to what the npm library returns from getSeries('E') / getSeries('F').

One entry per calendar month for which a fixing can be resolved.

{
"month": "2024-03",
"fixingDate": "2024-02-27",
"basePct": "3.892"
}
FieldTypeNotes
monthYYYY-MMCalendar month the rate applies to.
fixingDateYYYY-MM-DDAntepenultimate TARGET2 business day of the previous month.
basePctdecimal stringFinal, post-clamp base rate rounded to 3 decimals. For Série F: rounded mean clamped to [0, 2.5]. For Série E: rounded mean + 1.000 (the +1pp spread), clamped to [0, 3.5].

One entry per anchored quarter, from each subscription month through min(maturity, last published month).

{
"subscribed": "2024-03",
"subscriptionDate": "2024-03-01",
"quarterIndex": 8,
"quarterStartDate": "2026-03-01",
"quarterEndDate": "2026-06-01",
"yearsSinceSubscription": 2,
"basePct": "2.500",
"premiumTierYearsRange": "2-5",
"premiumPct": "0.25",
"annualRatePct": "2.750"
}
FieldTypeNotes
subscribedYYYY-MMCohort identifier; subscriptionDate always resolves to the 1st.
subscriptionDateYYYY-MM-DDAlways <subscribed>-01; see the day-of-month note below.
quarterIndexinteger0-based, counted from subscription.
quarterStartDate / quarterEndDateYYYY-MM-DDAnchored to the subscription day, with end-of-month roll-forward.
yearsSinceSubscriptionintegerWhole anniversary years elapsed at quarterStartDate.
basePctdecimal stringSame format as in monthlyBaseRates.
premiumTierYearsRangestringE.g. "2-5".
premiumPctdecimal stringPremium added to basePct.
annualRatePctdecimal stringComposite annual rate for that cohort × quarter.
  • All percentage fields are decimal strings (e.g. "2.500", not 2.5). This matches the npm library’s public API and avoids float drift across language boundaries.
  • subscribed is a calendar month (always normalised to YYYY-MM-01). IGCP’s anchored-quarter rule keys off the subscription day, so a precomputed table cannot represent every day-of-month cohort without exploding in size. Consumers needing day-precision should use the npm library or replicate the math from monthlyBaseRates plus the premium tiers in metadata.
  • cohortRates rows enumerate every anchored quarter from subscription through min(maturity, last published month). quarterEndDate is the next quarter’s quarterStartDate; both follow shiftMonths’ end-of-month roll-forward semantics.
import json, urllib.request
from decimal import Decimal, ROUND_HALF_EVEN
data = json.load(urllib.request.urlopen('https://primor.github.io/igcp-aforro/rates.json'))
rows = [r for r in data['series']['F']['cohortRates'] if r['subscribed'] == '2024-03']
units = Decimal('1000')
balance = units
irs = Decimal('0.28')
for r in rows:
annual = Decimal(r['annualRatePct']) / Decimal('100')
quarterly = annual / 4
gross = (balance * quarterly).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN)
withheld = (gross * irs).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN)
balance += gross - withheld
print(balance)