---
title: "Run a Monte Carlo retirement projection"
method: POST
path: "/v1/retirement/monte-carlo"
tags: ["Retirement"]
---

# Run a Monte Carlo retirement projection

`POST /v1/retirement/monte-carlo`

## Request

- Base URL: `https://api.numeratica.com`
- URL: `https://api.numeratica.com/v1/retirement/monte-carlo`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Simulates many market paths to estimate the probability that a plan's money lasts to the planning horizon, plus the distribution of ending balances. Deterministic: the same inputs and seed always return the same result and result_id.

## Request body

- MonteCarloRequest
  - `current_age` integer, required
  - `retirement_age` integer, required: Must be >= current_age.
  - `end_age` integer, required: Planning horizon; must be > retirement_age.
  - `current_balance` number, required
  - `annual_contribution` number, required: Nominal contribution per year while working.
  - `annual_spending` number, required: Annual withdrawal in today's dollars; inflated over time.
  - `expected_return` number: Arithmetic mean nominal annual return (> -1, <= 1). Required unless glide_path is given.
  - `return_volatility` number: Stddev of nominal annual return (0–1). Required unless glide_path is given.
  - `inflation` number: Annual inflation (> -1, <= 1).
  - `num_simulations` integer
  - `annual_fee_rate` number, double: All-in annual cost drag as a decimal (0.01 = 1%): advisory fee plus fund expense ratios. Applied as a reduction to the expected return each year, so it compounds inside the return distribution rather than acting as a separate withdrawal. Omitted or 0 means projections are GROSS of fees, which is the historical behaviour and leaves result_id unchanged. Costs matter: on the example above a 1% fee moves success probability by roughly 14 points.
  - `seed` integer: Optional RNG seed. If omitted, one is generated and returned so the run can be reproduced.
  - `glide_path` object[]: Optional age-varying return assumptions. The engine linearly interpolates expected_return/return_volatility between waypoints (held flat before the first and after the last), instead of the fixed scalar fields. Waypoints must be in strictly ascending age order. When provided, expected_return/return_volatility are not required.
    - `age` integer, required
    - `expected_return` number, required
    - `return_volatility` number, required
  - `assets` object[]: Optional correlated multi-asset projection. Each asset draws a correlated lognormal return (via a Cholesky of `correlations`) and the portfolio combines them by weight each year. When provided it supersedes the scalar and glide_path return inputs (and is mutually exclusive with glide_path); expected_return/return_volatility are then not required. A single asset at weight 1.0 reproduces the scalar run exactly. Weights must sum to 1.
    - `class` string: Free-form label (the engine treats assets positionally).
    - `weight` number, required
    - `expected_return` number, required
    - `return_volatility` number, required
  - `correlations` array[]: Asset-return correlation matrix (len(assets) square, symmetric, unit diagonal, entries in [-1,1], positive semi-definite). Required for 2+ assets; for a single asset an omitted matrix means the identity.
    - number[]
  - `rebalance` boolean: For the assets path: annual rebalancing to target weights (true, the default) vs buy-and-hold drift (false). Ignored without `assets`.
  - `guaranteed_income` object[]: Optional guaranteed-income streams (Social Security, pension, annuity) that offset retirement spending. Each retirement year the portfolio draw is max(0, spending − active streams). A stream may begin after retirement (the "retire at 60, claim Social Security at 67" bridge), so the portfolio correctly carries the full load during the gap years. Income only offsets the decumulation draw; surplus is not reinvested. Omit for the classic run.
    - `annual_amount` number, required: Today's dollars.
    - `start_age` integer, required: First age the stream pays (inclusive).
    - `end_age` integer: Last age it pays (inclusive); omit for life.
    - `cola` boolean: Inflation-adjust the amount (default true). false = fixed nominal, real value erodes.
  - `spending_rule` object: Optional dynamic-spending rule that replaces fixed inflation-adjusted spending with Guyton-Klinger guardrails (Guyton & Klinger 2006): each retirement year, cut spending if the withdrawal rate has risen above initial×(1+guard_upper), raise it if below initial×(1−guard_lower), and skip the inflation bump after a down year while spending exceeds the initial rate. Composes with guaranteed_income; not supported with the multi-asset model. Omit for fixed spending (byte-identical to the classic run).
    - `type` 'guyton_klinger', required: Rule family (only Guyton-Klinger today).
    - `initial_rate` number: Reference withdrawal rate the guardrails measure against. Omit (0) to derive it per path from the first retirement year's gross-spending rate.
    - `guard_upper` number: Capital-preservation band as a fraction of initial_rate.
    - `guard_lower` number: Prosperity band as a fraction of initial_rate.
    - `cut` number: Proportional spending cut when capital preservation trips.
    - `raise` number: Proportional spending raise when prosperity trips.
    - `final_years_frozen` integer: Suspend capital-preservation cuts in the last N years of the plan.

## Response `200`

Projection result

- MonteCarloResponse
  - `result_id` string: Deterministic id for this run (hash of inputs + seed + engine version).
  - `engine_version` string
  - `seed` integer
  - `inputs` MonteCarloRequest
    - `current_age` integer, required
    - `retirement_age` integer, required: Must be >= current_age.
    - `end_age` integer, required: Planning horizon; must be > retirement_age.
    - `current_balance` number, required
    - `annual_contribution` number, required: Nominal contribution per year while working.
    - `annual_spending` number, required: Annual withdrawal in today's dollars; inflated over time.
    - `expected_return` number: Arithmetic mean nominal annual return (> -1, <= 1). Required unless glide_path is given.
    - `return_volatility` number: Stddev of nominal annual return (0–1). Required unless glide_path is given.
    - `inflation` number: Annual inflation (> -1, <= 1).
    - `num_simulations` integer
    - `annual_fee_rate` number, double: All-in annual cost drag as a decimal (0.01 = 1%): advisory fee plus fund expense ratios. Applied as a reduction to the expected return each year, so it compounds inside the return distribution rather than acting as a separate withdrawal. Omitted or 0 means projections are GROSS of fees, which is the historical behaviour and leaves result_id unchanged. Costs matter: on the example above a 1% fee moves success probability by roughly 14 points.
    - `seed` integer: Optional RNG seed. If omitted, one is generated and returned so the run can be reproduced.
    - `glide_path` object[]: Optional age-varying return assumptions. The engine linearly interpolates expected_return/return_volatility between waypoints (held flat before the first and after the last), instead of the fixed scalar fields. Waypoints must be in strictly ascending age order. When provided, expected_return/return_volatility are not required.
      - `age` integer, required
      - `expected_return` number, required
      - `return_volatility` number, required
    - `assets` object[]: Optional correlated multi-asset projection. Each asset draws a correlated lognormal return (via a Cholesky of `correlations`) and the portfolio combines them by weight each year. When provided it supersedes the scalar and glide_path return inputs (and is mutually exclusive with glide_path); expected_return/return_volatility are then not required. A single asset at weight 1.0 reproduces the scalar run exactly. Weights must sum to 1.
      - `class` string: Free-form label (the engine treats assets positionally).
      - `weight` number, required
      - `expected_return` number, required
      - `return_volatility` number, required
    - `correlations` array[]: Asset-return correlation matrix (len(assets) square, symmetric, unit diagonal, entries in [-1,1], positive semi-definite). Required for 2+ assets; for a single asset an omitted matrix means the identity.
      - number[]
    - `rebalance` boolean: For the assets path: annual rebalancing to target weights (true, the default) vs buy-and-hold drift (false). Ignored without `assets`.
    - `guaranteed_income` object[]: Optional guaranteed-income streams (Social Security, pension, annuity) that offset retirement spending. Each retirement year the portfolio draw is max(0, spending − active streams). A stream may begin after retirement (the "retire at 60, claim Social Security at 67" bridge), so the portfolio correctly carries the full load during the gap years. Income only offsets the decumulation draw; surplus is not reinvested. Omit for the classic run.
      - `annual_amount` number, required: Today's dollars.
      - `start_age` integer, required: First age the stream pays (inclusive).
      - `end_age` integer: Last age it pays (inclusive); omit for life.
      - `cola` boolean: Inflation-adjust the amount (default true). false = fixed nominal, real value erodes.
    - `spending_rule` object: Optional dynamic-spending rule that replaces fixed inflation-adjusted spending with Guyton-Klinger guardrails (Guyton & Klinger 2006): each retirement year, cut spending if the withdrawal rate has risen above initial×(1+guard_upper), raise it if below initial×(1−guard_lower), and skip the inflation bump after a down year while spending exceeds the initial rate. Composes with guaranteed_income; not supported with the multi-asset model. Omit for fixed spending (byte-identical to the classic run).
      - `type` 'guyton_klinger', required: Rule family (only Guyton-Klinger today).
      - `initial_rate` number: Reference withdrawal rate the guardrails measure against. Omit (0) to derive it per path from the first retirement year's gross-spending rate.
      - `guard_upper` number: Capital-preservation band as a fraction of initial_rate.
      - `guard_lower` number: Prosperity band as a fraction of initial_rate.
      - `cut` number: Proportional spending cut when capital preservation trips.
      - `raise` number: Proportional spending raise when prosperity trips.
      - `final_years_frozen` integer: Suspend capital-preservation cuts in the last N years of the plan.
  - `result` object
    - `success_probability` number: Fraction of simulated paths whose money lasts to end_age.
    - `median_ending_balance` number
    - `ending_balance_percentiles` object
      - `p10` number
      - `p25` number
      - `p50` number
      - `p75` number
      - `p90` number
    - `median_depletion_age` integer, nullable: Median age money runs out among failed paths; omitted if none failed.
    - `num_simulations` integer
    - `spending` object: Present only when spending_rule was supplied: realized spending under the rule, in today's (real) dollars.
      - `realized` object: Distribution of each path's mean annual real spending.
        - `p10` number
        - `p25` number
        - `p50` number
        - `p75` number
        - `p90` number
      - `floor` number: 10th percentile of per-path minimum real spending (a bad sequence's worst year).
      - `ceiling` number: 90th percentile of per-path maximum real spending (a good sequence's best year).
      - `mean_cuts` number: Average capital-preservation cuts per path.
      - `mean_raises` number: Average prosperity raises per path.
  - `notices` string[]: Optional advisories about how the request was handled — e.g. a free-tier key that omitted num_simulations is told the count was defaulted to the cap. Absent when there's nothing to flag.
  - `disclaimer` string

## Other responses

- `400`: Invalid request
- `401`: Missing or invalid API key.
- `405`: Method not allowed
- `429`: The key's per-minute request budget is exceeded. Retry after the delay in the Retry-After header.
- `503`: A dependency (e.g. the key store) is temporarily unavailable, or the request exceeded its compute deadline. Retryable.

---

[API](https://skmtc.dev/numeratica/apis/numeratica-financial-planning-calculations-api.md) · [All operations](https://skmtc.dev/numeratica/apis/numeratica-financial-planning-calculations-api/llms.txt) · [OpenAPI document](https://skmtc.dev/numeratica/apis/numeratica-financial-planning-calculations-api/revisions/075bcfbd1ebf?raw)
