Developer documentation
Embed the calculator
The simplest option — drop our calculator into your page as an iframe. We serve a chromeless version (no INTEGRIS header or footer) at /calculatorSkinny, so it embeds cleanly into any layout.
<iframe
src="https://integris-mgt.com/calculatorSkinny"
width="100%"
height="700"
title="INTEGRIS PPP Calculator"
loading="lazy"
style="border: 0;"
></iframe>About 700 px tall fits most layouts comfortably. The calculator is responsive — give it whatever width you have available.
IPP variant
Append ?plan=ipp to render the IPP-styled embed. The legend reads “IPP” instead of “PPP®”, the age slider is restricted to 40+, and the stat panel reads “Your IPP advantage.”
<iframe
src="https://integris-mgt.com/calculatorSkinny?plan=ipp"
width="100%"
height="700"
title="INTEGRIS IPP Calculator"
loading="lazy"
style="border: 0;"
></iframe>Hide our call-to-action
By default the calculator shows an INTEGRIS “Get my personalized illustration” button under the result. If you’d rather add your own call-to-action, append ?cta=off to hide it — the sliders, chart, and result stats stay exactly the same.
<iframe
src="https://integris-mgt.com/calculatorSkinny?cta=off"
width="100%"
height="700"
title="INTEGRIS PPP Calculator"
loading="lazy"
style="border: 0;"
></iframe>The parameters compose — e.g. ?plan=ipp&cta=off renders the IPP embed with no INTEGRIS CTA.
Build your own UI with our API
If you want full control over the look and feel, call our projection endpoint directly and render the numbers however you like — your own chart library, a table, a bespoke layout.
Endpoint
POST https://integris-mgt.com/api/calculators/pppvsrsp
Content-Type: application/jsonRequest body
The first four fields are required and must be numeric. rrspBalance is optional. Out-of-range values are silently clamped to the bounds rather than rejected, so the API never refuses a real-looking request — mirror these bounds in your own UI if you want a clean user experience.
| Field | Range (inclusive) | Notes |
|---|---|---|
yearOfBirth | 1955 – 2008 | Four-digit year of birth. Bounds track the valuation year (2026) against ages 18–71. |
yearsOfService | 0 – 50 | Past T4 employment years to buy back |
currentSalary | 70000 – 500000 | Annual salary in dollars (not $1000s) |
averagePastSalary | 0 – 500000 | Average T4 income over the past service years |
rrspBalance | 0 – 2000000 | Optional. Current RRSP assets not locked in. Omit it to use our default assumption — see below. |
If you collect an age rather than a birth year, send age (18–71) instead of yearOfBirth and we convert it against the valuation year. yearOfBirth is the canonical field and the one to prefer: an age is only correct until the member’s next birthday, so a cached age-keyed result silently goes stale where a birth-year result does not. If you send both, yearOfBirth wins. Rows are keyed to the member’s age at the valuation date, so sLabels[0] can read one year below the age you sent — the same behaviour as the age slider on our own calculator.
The qualifying transfer
Buying back past service requires a qualifying transfer out of the member’s RRSP. The response reports it as qt, and rrspBalance drives it, so the three cases behave differently:
- You omit
rrspBalance— we assume the member holds exactly the qualifying transfer and project from there. The response echoes that assumed figure inrrspBalanceand setsrrspBalanceAssumed: true. This is the documented default, not a zero balance. - You supply a balance at or above
qt— both projections open at that balance andrrspBalanceAssumedisfalse. - You supply a balance below
qt— the projection still assumes the full transfer is completed, so the illustrated advantage may not be achievable. The response carries arrsp_below_qualifying_transferwarning:
{
"qt": 556000,
"rrspBalance": 100000,
"rrspBalanceAssumed": false,
"warnings": [
{
"code": "rrsp_below_qualifying_transfer",
"message": "The qualifying transfer required to purchase the past service shown in this illustration (approximately $556,000) exceeds the RRSP balance you entered. …"
}
]
}Response
The response is always 53 yearly rows starting at the member’s current age. Most callers want to truncate at the natural drawdown end — the first row after the peak where sPPP hits 0 — so the chart closes cleanly at retirement instead of trailing flat zeros. Take whatever window suits your layout.
{
"sStatus": "success",
"sLabels": ["45 years", "46 years", "47 years", "48 years", "...", "97 years"],
"sPPP": [600000, 648120, 699554, 754531, // 53 entries
/* … */],
"sRRSP": [600000, 631200, 664022, 698552,
/* … */],
"qt": 556000,
"rrspBalance": 600000,
"rrspBalanceAssumed": false,
"warnings": []
}sLabels— 53 year-of-age strings, starting at the input age ("{n} years").sPPP— projected total inside the pension plan at each of those ages, in dollars.sRRSP— projected total inside an equivalent RRSP at each of those ages, for comparison.qt— qualifying transfer required to fund the past-service purchase, in dollars.0whenyearsOfServiceis0.rrspBalance— the balance the projection actually ran on, after clamping and after our default assumption is applied.rrspBalanceAssumed—truewhen you sent no balance and we substitutedqt.warnings— array, usually empty. Each entry has a stablecodeand a display-readymessage.
Error responses
- 400 — request body is missing a required field or one of the values is non-numeric. Out-of-range numeric values do not return 400; they get clamped silently.
- 405 — the request method is not
POST. - 500 — engine error. Should not happen in normal use; ping us if you see one.
Example — curl
curl -X POST https://integris-mgt.com/api/calculators/pppvsrsp \
-H "Content-Type: application/json" \
-d '{"yearOfBirth":1981,"yearsOfService":20,"currentSalary":305000,"averagePastSalary":160000,"rrspBalance":600000}'Example — JavaScript
const res = await fetch('https://integris-mgt.com/api/calculators/pppvsrsp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
yearOfBirth: 1981, // 1955–2008, integer
yearsOfService: 20, // 0–50, integer
currentSalary: 305000, // 70000–500000, dollars (not $1000s)
averagePastSalary: 160000,// 0–500000, dollars
rrspBalance: 600000, // optional, 0–2000000, dollars
}),
});
const { sLabels, sPPP, sRRSP, qt, warnings } = await res.json();
// 53 yearly rows starting at the member's current age. Slice the window you
// want (e.g. the next 25 years of accumulation) and feed it into your chart.
// Always render these — see "The qualifying transfer" below.
for (const w of warnings) console.warn(w.code, w.message);Rate limits and caching
Cache aggressively.Responses are fully deterministic — the same input tuple (yearOfBirth, yearsOfService, currentSalary, averagePastSalary, rrspBalance) always returns the same 53 rows. If you’re using this from an LLM tool-use loop, cache by a hash of those five values; you almost never need to call twice for the same inputs.
Soft cap: ~10 requests / second per integration.No hard quota right now, but please keep concurrent bursts low so live users don’t hit a cold start while your bulk job is running. We’ll publish a real 429 response if it ever becomes a problem.
Cold start is ~13 seconds, warm calls are ~400 ms. The function caches its formula engine in memory between requests, so the first call after a quiet stretch or after a deploy can stall briefly. Treat one slow first call as normal, not a failure.
Backoff on 5xx.Exponential, max 3 retries. We pre-warm during weekday business hours but transient errors are still possible.
No PII.The four inputs are scalar numbers — never send names, emails, account numbers, or anything that could identify a real person.