Monitor every client's plan, nightly

An advisor platform's recurring job: rerun every client's core numbers and surface only the plans that moved. /v1/batch makes that one call per client — up to 20 calculations, dispatched in order, one result each — and determinism makes the "what moved?" diff nearly free.

1 · One client, one call

A 62-year-old couple, $1.2M saved, retiring at 65 with Social Security at 67: the nightly row is readiness (a seeded 2,000-path Monte Carlo — cheap and fully reproducible), IRMAA headroom on this year's MAGI, and conversion room in the current bracket.

curl -X POST https://api.numeratica.com/v1/batch \
  -H "Authorization: Bearer nmr_sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "requests": [
      { "path": "/v1/retirement/monte-carlo",
        "body": { "current_age": 62, "retirement_age": 65, "end_age": 95,
                  "current_balance": 1200000, "annual_contribution": 20000,
                  "annual_spending": 80000, "expected_return": 0.06,
                  "return_volatility": 0.11, "inflation": 0.025,
                  "num_simulations": 2000, "seed": 7,
                  "guaranteed_income": [{ "annual_amount": 42000, "start_age": 67 }] } },
      { "path": "/v1/medicare/irmaa",
        "body": { "irmaa_year": 2026, "filing_status": "mfj", "magi": 190000 } },
      { "path": "/v1/tax/bracket-room",
        "body": { "tax_year": 2026, "filing_status": "mfj", "taxable_income": 155000 } }
    ]
  }'

2 · The response

One entry per sub-request, order preserved, each exactly what the standalone endpoint returns — same handlers, same metering (one usage event per sub-request), same validation. A bad sub-request comes back as a 4xx inside the batch; the batch itself still returns 200.

{
  "result_id": "batch_c4fada937e72e252",  /* identical batches return an identical id */
  "engine_version": "batch-1.0.0",
  "result": {
    "results": [
      { "status": 200, "body": {
          "result_id": "mc_0af83c3608cf196c", "seed": 7,
          "result": { "success_probability": 0.918,
                      "median_ending_balance": 2587227.05,
                      "median_depletion_age": 90 } } },
      { "status": 200, "body": {
          "result_id": "irmaa_9d0ff229ae054efb",
          "result": { "tier": 0, "part_b_total": 202.90,
                      "annual_surcharge": 0,
                      "headroom_to_next_tier": 28000 } } },
      { "status": 200, "body": {
          "result_id": "bracket_f7f57d47efc36f93",
          "result": { "marginal_rate": 0.22,
                      "ordinary_room": [ { "rate": 0.22, "room": 56400 } ],
                      "ltcg_fifteen_room": 458700 } } }
    ]
  },
  "disclaimer": "Hypothetical projection from a stochastic model; simulated results do not reflect actual investment performance and are not a guarantee of future results. Informational only; not financial advice."
}

3 · The change-detection trick

Every sub-result's result_id is a content hash of its inputs and engine version. So the nightly diff is: store yesterday's ids, compare today's. Same ids ⇒ nothing moved — same client data, same tables, same engine — skip the row entirely. An id changed ⇒ either the client's inputs changed or an engine/table was updated; recompute the deltas and flag the row for a human. No field-by-field comparison, no tolerance thresholds.

Operational notes, all deliberate: rate limiting is all-or-nothing (a batch of N reserves N tokens up front — a 429 means the whole batch waited, never a partial run), the batch shares one 25-second budget (a seeded 2,000-path Monte Carlo uses a fraction of it), and the 20-sub-request cap means one call per client, chunked — 500 clients is 500 calls, which is what the per-key rate limit and Retry-After are for. Sweep nightly at 2,000 simulations for speed; run the full-depth Monte Carlo monthly or when a row flags.

4 · Render it

The dashboard row the sweep feeds — one line per client, only flagged rows expanded.

Rendered output
<div style="font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#1a2230;max-width:600px">
  <div style="font-size:14px;color:#5b6675;margin-bottom:8px">Nightly plan sweep · 2026-07-09 · 1 of 20 clients shown</div>
  <div style="border:1px solid #e6e9ef;border-radius:10px;padding:12px">
    <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px">
      <div style="font-weight:600">Client #1047 · retiring 2029</div>
      <div style="font-size:12px;color:#16a34a;font-weight:600">✓ no change since yesterday</div>
    </div>
    <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;font-size:13px">
      <div>
        <div style="color:#5b6675;font-size:12px">Readiness</div>
        <div style="font-size:20px;font-weight:700">91.8%</div>
        <div style="color:#5b6675;font-size:12px">median end $2.59M</div>
      </div>
      <div>
        <div style="color:#5b6675;font-size:12px">IRMAA headroom</div>
        <div style="font-size:20px;font-weight:700">$28,000</div>
        <div style="color:#5b6675;font-size:12px">tier 0 · no surcharge</div>
      </div>
      <div>
        <div style="color:#5b6675;font-size:12px">22% bracket room</div>
        <div style="font-size:20px;font-weight:700">$56,400</div>
        <div style="color:#5b6675;font-size:12px">conversion window open</div>
      </div>
    </div>
  </div>
  <div style="color:#5b6675;font-size:12px;margin-top:6px">result_ids mc_0af83c3608cf196c · irmaa_9d0ff229ae054efb · bracket_f7f57d47efc36f93 · hypothetical projection, not a guarantee of future results · not financial advice</div>
</div>

The pieces of the row: the readiness check, the IRMAA planner, and Roth conversion when the bracket-room column says the window is open. Agents can drive the same sweep through the MCP server.

← All guides