Tutorials
Three full workflows that combine multiple endpoints into useful answers. Each one starts from a working bearer token (see Quickstart §1) and ends with shippable data.
Compare Roth conversion strategies
You have a household with $1.8M of pre-tax traditional IRA, a 65-year-old client who'll be RMD-bound in eight years, and a current marginal rate of 12% (low — they just retired). You want to know how much lifetime tax savings each Roth strategy delivers and which beats the no-conversion baseline.
The recipe: run the optimizer three times — once per strategy — and diff the lifetime-savings totals.
const baseUrl = 'https://legacy.mavenfin.tech/isrestapi19/api/v1';
const auth = { Authorization: 'Bearer ' + token };
async function optimize(strategy, targetBracket) {
const res = await fetch(`${baseUrl}/households/1/plans/1/roth-optimize`, {
method: 'POST',
headers: { ...auth, 'Content-Type': 'application/json' },
body: JSON.stringify({
strategy, targetBracket,
startYear: 2026, endYear: 2034,
}),
});
return res.json();
}
const [thr, fill, dyn] = await Promise.all([
optimize('thresholdRoth', 22),
optimize('bracketFill', 22),
optimize('dynamic', 24),
]);
console.table([
{ strategy: 'thresholdRoth', savings: thr.totalLifetimeTaxSavings },
{ strategy: 'bracketFill', savings: fill.totalLifetimeTaxSavings },
{ strategy: 'dynamic', savings: dyn.totalLifetimeTaxSavings },
]);
import requests
from concurrent.futures import ThreadPoolExecutor
base = 'https://legacy.mavenfin.tech/isrestapi19/api/v1'
hdr = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}
def optimize(strategy, target):
return requests.post(
f'{base}/households/1/plans/1/roth-optimize',
json={'strategy': strategy, 'targetBracket': target,
'startYear': 2026, 'endYear': 2034},
headers=hdr, timeout=60,
).json()
with ThreadPoolExecutor(3) as ex:
thr, fill, dyn = ex.map(
lambda a: optimize(*a),
[('thresholdRoth', 22), ('bracketFill', 22), ('dynamic', 24)],
)
for name, r in [('threshold', thr), ('bracketFill', fill), ('dynamic', dyn)]:
print(f'{name:>14}: ${r["totalLifetimeTaxSavings"]:>12,.0f}')
For most retirees we see bracketFill at 22% deliver 60–80% of the absolute savings of
dynamic, in a small fraction of the cognitive overhead. The dynamic optimizer wins on
edge cases — IRMAA-cliff households, large-pension households, or anyone with material wage income past 65.
Build a longevity-risk chart
Longevity risk is the probability of outliving your portfolio. With Monte Carlo, it's a clean computation: run the simulation, then bucket the terminal portfolio values to draw a probability curve.
The shortcut is to make a single call with a high runs count, request the percentile fan,
and chart the response. P10 tells you the catastrophic case; P50 the median; P90 the windfall.
const res = await fetch(
'https://legacy.mavenfin.tech/isrestapi19/api/v1/households/1/plans/1/montecarlo',
{
method: 'POST',
headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' },
body: JSON.stringify({ runs: 5000, seed: 7, equityReturn: 0.07, equityVol: 0.18 }),
},
);
const mc = await res.json();
// Feed straight into Chart.js / D3 / Plotly as the y-axis values
const data = [
{ label: 'P10', y: mc.p10 },
{ label: 'P25', y: mc.p25 },
{ label: 'P50', y: mc.p50 },
{ label: 'P75', y: mc.p75 },
{ label: 'P90', y: mc.p90 },
];
console.log(`Success rate: ${(mc.successRate * 100).toFixed(1)}%`);
import requests, json
mc = requests.post(
'https://legacy.mavenfin.tech/isrestapi19/api/v1/households/1/plans/1/montecarlo',
headers={'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'},
json={'runs': 5000, 'seed': 7,
'equityReturn': 0.07, 'equityVol': 0.18},
timeout=120,
).json()
print(f"success: {mc['successRate']*100:.1f}%")
for k in ('p10','p25','p50','p75','p90'):
print(f" {k.upper()}: ${mc[k]:>12,.0f}")
seed across UI sessions so users see stable numbers
between page loads. Bump the seed (or remove it) only when explicitly exploring variability.
Calibrate Monte Carlo to a household
The default 7%/18% equity, 3%/5% bond assumptions are conservative-modern. For households with material international tilt, factor exposure, or alternative allocations, you'll want to override them. The deterministic projection's asset-allocation strategy is your input; the historical realized return is your calibration target.
The recipe: read the plan's effective allocation, look up corresponding mean/vol pairs from your own capital-market-assumptions table, then pass those into Monte Carlo.
import requests
base = 'https://legacy.mavenfin.tech/isrestapi19/api/v1'
hdr = {'Authorization': f'Bearer {token}'}
# Your house view of capital-market assumptions
CMA = {
'us_lg_cap': { 'r': 0.068, 'v': 0.175 },
'us_sm_cap': { 'r': 0.078, 'v': 0.235 },
'intl_dev': { 'r': 0.071, 'v': 0.195 },
'em': { 'r': 0.085, 'v': 0.270 },
'us_agg_bond': { 'r': 0.034, 'v': 0.055 },
'cash': { 'r': 0.020, 'v': 0.010 },
}
# Inspect the plan's effective allocation
inputs = requests.get(f'{base}/households/1/inputs', headers=hdr, timeout=30).json()
# (Pretend) extract the planned equity/bond split from inputs.aas[0].targetWeights
eq_weight = 0.60
bond_weight = 0.40
# Weighted blend
eq_r = (0.55*CMA['us_lg_cap']['r']
+ 0.15*CMA['us_sm_cap']['r']
+ 0.20*CMA['intl_dev']['r']
+ 0.10*CMA['em']['r'])
eq_v = 0.185 # use a portfolio-vol estimator in production
mc = requests.post(
f'{base}/households/1/plans/1/montecarlo',
headers={**hdr, 'Content-Type': 'application/json'},
json={
'runs': 2500, 'seed': 1,
'equityReturn': round(eq_r, 4),
'equityVol': round(eq_v, 4),
'bondReturn': CMA['us_agg_bond']['r'],
'bondVol': CMA['us_agg_bond']['v'],
},
timeout=120,
).json()
print(f"calibrated success: {mc['successRate']*100:.1f}%")
For production, the equity-vol blend needs the full asset-asset covariance matrix, not just a marginal-vol average. The simple weighted version above is fine for a first cut and to show users that custom CMAs are being respected.