---
title: "Enqueue Simulation Run"
method: POST
path: "/api/runs"
tags: ["runs"]
---

# Enqueue Simulation Run

`POST /api/runs`

## Request

- Base URL: `https://api.retirementlab.app` (the document declares no server; this is the origin it was published from)
- URL: `https://api.retirementlab.app/api/runs`
- Auth: HTTP bearer

## Request body

- RunRequest
  - `config_name` string
  - `simulation` SimulationConfigPayload: Simulation payload with all money values represented in full currency units.
    - `initial_year` integer, required
    - `initial_net_worth` number, required: Starting portfolio value in full local currency units.
    - `baseline_annual_expenses` number, required: Baseline annual spending in full local currency units.
    - `additional_expenses` SimulationExpensePayload[], required
      - `description` string, required
      - `cost` number, required: Expense amount in full local currency units.
      - `begin_year` integer, required: First year the expense applies (inclusive).
      - `end_year` integer, required: Last year the expense applies (inclusive). For a single-year expense, set begin_year == end_year. The API services layer converts to the engine's half-open ``[begin, end)`` interval.
      - `item_id` string, uuid
      - `seeded_from` string: Marker identifying a helper that generated this row (e.g. 'healthcare_defaults'). Absent on user-authored rows.
    - `additional_income` SimulationIncomePayload[], required
      - `description` string, required
      - `income` number, required: Income amount in full local currency units.
      - `begin_year` integer, required: First year the income applies (inclusive).
      - `end_year` integer, required: Last year the income applies (inclusive). For a single-year income, set begin_year == end_year. The API services layer converts to the engine's half-open ``[begin, end)`` interval.
      - `type` 'employment' | 'social_security' | 'public_pension' | 'private_pension' | 'rental' | 'other' | 'asset_sale'
      - `source_country` string
      - `claim_age` integer
      - `benefit_source` string
      - `cost_basis` number
      - `person` 'A' | 'B'
      - `item_id` string, uuid
      - `ss_benefit_basis_mode` 'fra_benefit' | 'ssa_estimate_at_age' | 'already_receiving'
      - `ss_monthly_benefit_basis` number
      - `ss_benefit_basis_age` integer
      - `ss_fra_age` integer
    - `country` string, required
    - `citizenship` string
    - `avg_return` number, required
    - `volatility` number, required
    - `approximate_yield_rate` number, required
    - `taxable_balance` number, required
    - `tax_deferred_balance` number, required
    - `tax_free_balance` number, required
    - `taxable_cost_basis` number, required
    - `accounts` SimulationAccountPayload[]
      - `wrapper_id` string, required
      - `balance` number, required: Account balance in full local currency units.
      - `cost_basis` number
      - `person` 'A' | 'B'
      - `item_id` string, uuid
      - `cross_border_tax_facts` CrossBorderTaxFactsPayload: Wrapper-tax facts the simulator cannot infer from a balance. Every field is optional: ``None`` (or absent) means the user has not asserted the fact yet. Empty payloads round-trip as ``None`` on the parent ``SimulationAccountPayload`` so existing scenarios stay backwards compatible.
        - `roth_contribution_basis` number
        - `qualified_roth_distribution` boolean
        - `canada_roth_election` boolean
        - `post_residency_contribution` boolean
        - `payment_form` 'lump_sum' | 'periodic'
      - `withdrawal_access` AccountAccessRulePayload
        - `mode` 'default' | 'available_now' | 'penalty_free_from' | 'locked_until'
        - `age` integer
        - `year` integer
        - `rule_of_55_eligible` boolean
      - `securities_holding_eligibility_override` 'eligible' | 'ineligible'
    - `withdrawal_order` 'taxable_first' | 'tax_deferred_first' | 'tax_free_first'
    - `tax_filing_status` 'single' | 'married_filing_jointly' | 'head_of_household'
    - `wealth_tax_region` string
    - `tax_regime` string
    - `subnational_region` string
    - `taxable_portfolio_source` 'domestic' | 'foreign'
    - `tax_regime_years_remaining` integer
    - `portfolio_dividend_qualified_pct` number
    - `portfolio_gains_longterm_pct` number
    - `primary_residence_value` number
    - `primary_residence_debt` number
    - `non_portfolio_property_value` number
    - `regime_eligibility_facts` object
    - `tax_profile_extended` object
    - `medicare_levy_applicable` boolean
    - `super_characterization` string
    - `remittance_basis` boolean
    - `tax_profile_explicit_fields` string[]
    - `horizon` integer
    - `n_scenarios` integer
    - `inflation` number
    - `tail_profile` 'none' | 'historical' | 'severe': Shape of each asset's annual returns. 'historical' (default): skew and tail weight fitted to long US histories; 'none': plain log-normal; 'severe': the heaviest stock-market tail the 1871-2024 record cannot rule out. Expected returns and volatilities are unchanged by the choice.
    - `market_turbulence` 'calmer' | 'historical' | 'rougher': How big each year's market ups and downs are. 'historical' (default): US stock volatility over 1871-2024; 'calmer': the calmest 30-year stretch on record (1871-1900); 'rougher': the roughest (1927-1956). Scales every asset's volatility; expected compound returns are unchanged.
    - `skewness_start` number: Deprecated and ignored; use tail_profile.
    - `skewness_factor` number: Deprecated and ignored; use tail_profile.
    - `minimum_ending_balance` number
    - `primary_heir_country` string
    - `retirement_year` integer
    - `annual_savings` number
    - `age` integer
    - `plan_to_age` integer
    - `person_b_retirement_year` integer
    - `person_b_age` integer
    - `person_b_plan_to_age` integer
    - `person_labels` string[]
    - `portfolio_preset` string
    - `portfolio_allocation` object[]
    - `glide_path` GlidePathWaypointPayload[]
      - `age` integer, required
      - `allocation` object[], required
      - `item_id` string, uuid
    - `rebalancing_mode` 'annual' | 'threshold' | 'none'
    - `rebalancing_threshold` number
    - `regime_aware` boolean
    - `simulation_mode` 'monte_carlo_regime' | 'monte_carlo_iid' | 'historical'
    - `withdrawal_strategy` string
    - `withdrawal_strategy_params` object
    - `return_assumption_preset` string
    - `return_overrides` object
    - `aca_subsidies_enabled` boolean
    - `household_size` integer
    - `aca_state` string
    - `medicare_enabled` boolean
    - `irmaa_enabled` boolean
    - `tax_loss_harvesting_enabled` boolean
    - `residency_schedule` object[]
    - `relocation_events` RelocationEventPayload[]
      - `year` integer, required
      - `to_country` string, required
      - `regime` string
      - `item_id` string, uuid
      - `sell_primary` boolean
      - `buy_primary_value` number
      - `buy_primary_mortgage` number
      - `fx_rate` number
      - `fx_source_currency` string
      - `fx_destination_currency` string
      - `fx_assumption_source` 'caller_supplied_fixed_rate'
    - `baseline_adjustments` BaselineAdjustmentPayload[]
      - `year` integer, required
      - `new_baseline` number, required
      - `reason` string
      - `item_id` string, uuid
      - `derived_from` string
    - `roth_conversions` RothConversionPayload[]
      - `year` integer, required
      - `amount` number, required
      - `from_wrapper_id` string, required
      - `to_wrapper_id` string, required
      - `item_id` string, uuid
      - `derived_from` string
    - `article24_relief` Article24ReliefFactsPayload: France-US tax treaty Article 24 portfolio relief facts. These are user-confirmed simulation facts, not a determination of legal eligibility — user-facing copy referencing this block must say "based on your inputs" and avoid advisory terms. ``eligibility_status`` defaults to ``"unknown"``, which must never silently activate relief downstream. A globally diversified portfolio can contain both qualifying and non-qualifying assets, so eligibility is recorded as fractions rather than a single domestic/foreign flag.
      - `schema_version` integer
      - `eligibility_status` 'unknown' | 'eligible' | 'ineligible'
      - `qualifying_investment_income_fraction` number
      - `qualifying_capital_gains_fraction` number
      - `us_source_or_asset_qualification_confirmed` boolean
      - `beneficial_ownership_confirmed` boolean
      - `us_reporting_compliance_confirmed` boolean
    - `puma_csm` PumaCsmAssumptionPayload: France PUMa cotisation subsidiaire maladie (CSM) eligibility assumption. A scenario-level state, separate from the per-adult ``puma_household_adults`` entries. ``"unknown"`` (the default) must never silently activate CSM liability downstream.
      - `eligibility_status` 'unknown' | 'eligible' | 'ineligible'
    - `puma_household_adults` PumaHouseholdAdultPayload[]
      - `household_relationship` 'self' | 'spouse' | 'pacs_partner' | 'other_adult'
      - `income_attribution` 'per_adult' | 'equal_household'
      - `person` 'A' | 'B'
      - `coverage_start_date` string, date
      - `coverage_end_date` string, date
      - `item_id` string, uuid
  - `idempotency_key` string
  - `withdrawal_baseline_run_id` integer
  - `currency_context` object: MCP-owned currency provenance (input_currency, simulation_currency, and FX receipts). Set by the MCP layer; ignored for non-MCP callers.

## Response `202`

Successful Response

- RunStatusResponse
  - `id` integer, required
  - `withdrawal_baseline_run_id` integer
  - `config_id` integer
  - `config_name` string
  - `label` string
  - `status` 'queued' | 'running' | 'succeeded' | 'failed', required
  - `run_kind` 'simulation' | 'sweep' | 'grid' | 'roth_conversion_explorer'
  - `run_source` string
  - `sweep_variable` 'retirement_year' | 'annual_savings' | 'portfolio_preset' | 'baseline_annual_expenses' | 'initial_net_worth' | 'country' | 'claim_age' | 'bond_tilt_pp'
  - `sweep_values` string[]
  - `col_enabled` boolean
  - `col_adjustments` object
  - `sweep_target_income_id` string
  - `stats` RunStats
    - `baseline_withdrawal_rate` number, required
    - `total_scenarios` integer
    - `probability_of_failure` number, required
    - `minimum_ending_balance` number
    - `country` string
    - `success_probability` number
    - `net_worth` PercentileStats, required
      - `average` number
      - `p10` number
      - `p50` number
      - `p90` number
    - `ending_portfolio` PercentileStats
      - `average` number
      - `p10` number
      - `p50` number
      - `p90` number
    - `depletion` DepletionMetadata
      - `p10_depleted` boolean
      - `p50_depleted` boolean
      - `p90_depleted` boolean
    - `returns` PercentileStats, required
      - `average` number
      - `p10` number
      - `p50` number
      - `p90` number
    - `withdrawal_rates` PercentileStats, required
      - `average` number
      - `p10` number
      - `p50` number
      - `p90` number
    - `tax_rates` PercentileStats, required
      - `average` number
      - `p10` number
      - `p50` number
      - `p90` number
    - `tax_components` TaxComponentStats
      - `income` PercentileStats, required
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `investment_income` PercentileStats, required
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `capital_gains` PercentileStats, required
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `wealth` PercentileStats, required
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `total` PercentileStats, required
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `surcharges` PercentileStats
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
    - `tax_summary` TaxSummary
      - `annual_average_total` number, required
      - `display_basis` 'average_annual_tax_per_simulated_scenario'
      - `breakdown` TaxSummaryLine[]
        - `component` string, required
        - `label` string, required
        - `annual_average_amount` number, required
    - `first_year_taxes` FirstYearTaxSummary
      - `median_total` number, required
      - `display_basis` 'median_first_year'
      - `breakdown` FirstYearTaxSummaryLine[]
        - `component` string, required
        - `label` string, required
        - `median_amount` number, required
    - `tax_impact` TaxImpact
      - `tax_drag` PercentileStats, required
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `wealth_impact` PercentileStats, required
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `safety_impact` number, required
      - `failure_rate_with_tax` number, required
      - `failure_rate_without_tax` number, required
      - `comparison_basis` string
    - `failed_runs` FailedRunStats, required
      - `returns` PercentileStatsWithMax, required
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
        - `max` number
      - `ages` PercentileStats, required
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `failure_years` PercentileStats
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `failure_calendar_years` PercentileStats
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `first_bad_index` integer, required
    - `legacy` LegacySummary
      - `heir_country` string
      - `median_pre_tax` number
      - `median_tax` number
      - `median_post_tax` number
      - `average_pre_tax` number
      - `average_tax` number
      - `average_post_tax` number
      - `pre_tax` PercentileStats
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `tax` PercentileStats
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
      - `post_tax` PercentileStats
        - `average` number
        - `p10` number
        - `p50` number
        - `p90` number
    - `median_legacy_pre_tax` number
    - `median_legacy_tax` number
    - `median_legacy_post_tax` number
    - `sampled_timelines` SampledTimeline[]
      - `scenario_index` integer, required
      - `is_failure` boolean, required
      - `points` SampledTimelinePoint[]
        - `year_index` integer, required
        - `year` integer, required
        - `net_worth` number, required
      - `returns` number[]
      - `depletion_year_index` integer
      - `cohort_start_year` integer
    - `percentile_scenarios` PercentileScenarios: Scenario indices anchored to each percentile across the full run population. The year-by-year selector uses these to switch the ledger to the actual P10/P25/P50/P75/P90 path, rather than approximating from the capped ``sampled_timelines`` subset.
      - `p10` integer
      - `p25` integer
      - `p50` integer
      - `p75` integer
      - `p90` integer
      - `p10_depletion_year_index` integer
      - `p25_depletion_year_index` integer
      - `p50_depletion_year_index` integer
      - `p75_depletion_year_index` integer
      - `p90_depletion_year_index` integer
    - `percentile_timeseries` PercentileTimeseriesRow[]
      - `year` integer, required
      - `p10` number, required
      - `p25` number, required
      - `p50` number, required
      - `p75` number, required
      - `p90` number, required
    - `per_asset_simulation` boolean
    - `rebalancing_mode` string
    - `regime_aware` boolean
    - `regime_summary` object
    - `return_assumption_preset` string
    - `total_cohorts` integer
    - `succeeding_cohorts` integer
    - `failing_cohorts` integer
    - `failing_cohort_years` integer[]
    - `worst_cohort` WorstCohortStats
      - `start_year` integer, required
      - `depletion_year` integer, required
      - `depletion_calendar_year` integer, required
    - `france_assumption_resolution` FranceAssumptionResolution
      - `context` 'personal_scenario' | 'country_sweep' | 'published_benchmark', required
      - `resolver_version` string, required
      - `benchmark_pack_id` string
      - `benchmark_pack_version` integer
      - `primary` FranceVariantReceipt, required: One resolved France run variant (primary or sensitivity).
        - `resolver_version` string, required
        - `context` 'personal_scenario' | 'country_sweep' | 'published_benchmark', required
        - `variant` string, required
        - `label` string, required
        - `benchmark_pack_id` string
        - `benchmark_pack_version` integer
        - `effective_assumptions` FranceEffectiveAssumption[]
          - `name` string, required
          - `value` unknown
          - `provenance` 'user_confirmed' | 'unresolved' | 'modeled_estimate' | 'benchmark_pack' | 'benchmark_sensitivity', required
          - `reason` string, required
        - `tax_policy_version` string
        - `tax_policy_sources` string[]
      - `sensitivities` FranceVariantReceipt[]
        - `resolver_version` string, required
        - `context` 'personal_scenario' | 'country_sweep' | 'published_benchmark', required
        - `variant` string, required
        - `label` string, required
        - `benchmark_pack_id` string
        - `benchmark_pack_version` integer
        - `effective_assumptions` FranceEffectiveAssumption[]
          - `name` string, required
          - `value` unknown
          - `provenance` 'user_confirmed' | 'unresolved' | 'modeled_estimate' | 'benchmark_pack' | 'benchmark_sensitivity', required
          - `reason` string, required
        - `tax_policy_version` string
        - `tax_policy_sources` string[]
    - `france_sensitivity_outcomes` FranceSensitivityOutcome[]
      - `variant` string, required
      - `label` string, required
      - `probability_of_success` number
      - `stats` FranceSensitivityStats: Compact slice of a sensitivity variant's run stats. The worker stores the sensitivity's *full* stats blob; only the handful of aggregate figures the UI renders side by side are re-serialized here so a France run response does not carry a second copy of every sampled timeline.
        - `probability_of_failure` number
        - `success_probability` number
        - `net_worth` PercentileStats
          - `average` number
          - `p10` number
          - `p50` number
          - `p90` number
        - `ending_portfolio` PercentileStats
          - `average` number
          - `p10` number
          - `p50` number
          - `p90` number
        - `tax_rates` PercentileStats
          - `average` number
          - `p10` number
          - `p50` number
          - `p90` number
      - `assumptions` FranceVariantReceipt: One resolved France run variant (primary or sensitivity).
        - `resolver_version` string, required
        - `context` 'personal_scenario' | 'country_sweep' | 'published_benchmark', required
        - `variant` string, required
        - `label` string, required
        - `benchmark_pack_id` string
        - `benchmark_pack_version` integer
        - `effective_assumptions` FranceEffectiveAssumption[]
          - `name` string, required
          - `value` unknown
          - `provenance` 'user_confirmed' | 'unresolved' | 'modeled_estimate' | 'benchmark_pack' | 'benchmark_sensitivity', required
          - `reason` string, required
        - `tax_policy_version` string
        - `tax_policy_sources` string[]
    - `currency_segments` object[]
    - `relocation_currency_receipts` object[]
    - `final_headline_currency` string
  - `simulation` SimulationConfigPayload: Simulation payload with all money values represented in full currency units.
    - `initial_year` integer, required
    - `initial_net_worth` number, required: Starting portfolio value in full local currency units.
    - `baseline_annual_expenses` number, required: Baseline annual spending in full local currency units.
    - `additional_expenses` SimulationExpensePayload[], required
      - `description` string, required
      - `cost` number, required: Expense amount in full local currency units.
      - `begin_year` integer, required: First year the expense applies (inclusive).
      - `end_year` integer, required: Last year the expense applies (inclusive). For a single-year expense, set begin_year == end_year. The API services layer converts to the engine's half-open ``[begin, end)`` interval.
      - `item_id` string, uuid
      - `seeded_from` string: Marker identifying a helper that generated this row (e.g. 'healthcare_defaults'). Absent on user-authored rows.
    - `additional_income` SimulationIncomePayload[], required
      - `description` string, required
      - `income` number, required: Income amount in full local currency units.
      - `begin_year` integer, required: First year the income applies (inclusive).
      - `end_year` integer, required: Last year the income applies (inclusive). For a single-year income, set begin_year == end_year. The API services layer converts to the engine's half-open ``[begin, end)`` interval.
      - `type` 'employment' | 'social_security' | 'public_pension' | 'private_pension' | 'rental' | 'other' | 'asset_sale'
      - `source_country` string
      - `claim_age` integer
      - `benefit_source` string
      - `cost_basis` number
      - `person` 'A' | 'B'
      - `item_id` string, uuid
      - `ss_benefit_basis_mode` 'fra_benefit' | 'ssa_estimate_at_age' | 'already_receiving'
      - `ss_monthly_benefit_basis` number
      - `ss_benefit_basis_age` integer
      - `ss_fra_age` integer
    - `country` string, required
    - `citizenship` string
    - `avg_return` number, required
    - `volatility` number, required
    - `approximate_yield_rate` number, required
    - `taxable_balance` number, required
    - `tax_deferred_balance` number, required
    - `tax_free_balance` number, required
    - `taxable_cost_basis` number, required
    - `accounts` SimulationAccountPayload[]
      - `wrapper_id` string, required
      - `balance` number, required: Account balance in full local currency units.
      - `cost_basis` number
      - `person` 'A' | 'B'
      - `item_id` string, uuid
      - `cross_border_tax_facts` CrossBorderTaxFactsPayload: Wrapper-tax facts the simulator cannot infer from a balance. Every field is optional: ``None`` (or absent) means the user has not asserted the fact yet. Empty payloads round-trip as ``None`` on the parent ``SimulationAccountPayload`` so existing scenarios stay backwards compatible.
        - `roth_contribution_basis` number
        - `qualified_roth_distribution` boolean
        - `canada_roth_election` boolean
        - `post_residency_contribution` boolean
        - `payment_form` 'lump_sum' | 'periodic'
      - `withdrawal_access` AccountAccessRulePayload
        - `mode` 'default' | 'available_now' | 'penalty_free_from' | 'locked_until'
        - `age` integer
        - `year` integer
        - `rule_of_55_eligible` boolean
      - `securities_holding_eligibility_override` 'eligible' | 'ineligible'
    - `withdrawal_order` 'taxable_first' | 'tax_deferred_first' | 'tax_free_first'
    - `tax_filing_status` 'single' | 'married_filing_jointly' | 'head_of_household'
    - `wealth_tax_region` string
    - `tax_regime` string
    - `subnational_region` string
    - `taxable_portfolio_source` 'domestic' | 'foreign'
    - `tax_regime_years_remaining` integer
    - `portfolio_dividend_qualified_pct` number
    - `portfolio_gains_longterm_pct` number
    - `primary_residence_value` number
    - `primary_residence_debt` number
    - `non_portfolio_property_value` number
    - `regime_eligibility_facts` object
    - `tax_profile_extended` object
    - `medicare_levy_applicable` boolean
    - `super_characterization` string
    - `remittance_basis` boolean
    - `tax_profile_explicit_fields` string[]
    - `horizon` integer
    - `n_scenarios` integer
    - `inflation` number
    - `tail_profile` 'none' | 'historical' | 'severe': Shape of each asset's annual returns. 'historical' (default): skew and tail weight fitted to long US histories; 'none': plain log-normal; 'severe': the heaviest stock-market tail the 1871-2024 record cannot rule out. Expected returns and volatilities are unchanged by the choice.
    - `market_turbulence` 'calmer' | 'historical' | 'rougher': How big each year's market ups and downs are. 'historical' (default): US stock volatility over 1871-2024; 'calmer': the calmest 30-year stretch on record (1871-1900); 'rougher': the roughest (1927-1956). Scales every asset's volatility; expected compound returns are unchanged.
    - `skewness_start` number: Deprecated and ignored; use tail_profile.
    - `skewness_factor` number: Deprecated and ignored; use tail_profile.
    - `minimum_ending_balance` number
    - `primary_heir_country` string
    - `retirement_year` integer
    - `annual_savings` number
    - `age` integer
    - `plan_to_age` integer
    - `person_b_retirement_year` integer
    - `person_b_age` integer
    - `person_b_plan_to_age` integer
    - `person_labels` string[]
    - `portfolio_preset` string
    - `portfolio_allocation` object[]
    - `glide_path` GlidePathWaypointPayload[]
      - `age` integer, required
      - `allocation` object[], required
      - `item_id` string, uuid
    - `rebalancing_mode` 'annual' | 'threshold' | 'none'
    - `rebalancing_threshold` number
    - `regime_aware` boolean
    - `simulation_mode` 'monte_carlo_regime' | 'monte_carlo_iid' | 'historical'
    - `withdrawal_strategy` string
    - `withdrawal_strategy_params` object
    - `return_assumption_preset` string
    - `return_overrides` object
    - `aca_subsidies_enabled` boolean
    - `household_size` integer
    - `aca_state` string
    - `medicare_enabled` boolean
    - `irmaa_enabled` boolean
    - `tax_loss_harvesting_enabled` boolean
    - `residency_schedule` object[]
    - `relocation_events` RelocationEventPayload[]
      - `year` integer, required
      - `to_country` string, required
      - `regime` string
      - `item_id` string, uuid
      - `sell_primary` boolean
      - `buy_primary_value` number
      - `buy_primary_mortgage` number
      - `fx_rate` number
      - `fx_source_currency` string
      - `fx_destination_currency` string
      - `fx_assumption_source` 'caller_supplied_fixed_rate'
    - `baseline_adjustments` BaselineAdjustmentPayload[]
      - `year` integer, required
      - `new_baseline` number, required
      - `reason` string
      - `item_id` string, uuid
      - `derived_from` string
    - `roth_conversions` RothConversionPayload[]
      - `year` integer, required
      - `amount` number, required
      - `from_wrapper_id` string, required
      - `to_wrapper_id` string, required
      - `item_id` string, uuid
      - `derived_from` string
    - `article24_relief` Article24ReliefFactsPayload: France-US tax treaty Article 24 portfolio relief facts. These are user-confirmed simulation facts, not a determination of legal eligibility — user-facing copy referencing this block must say "based on your inputs" and avoid advisory terms. ``eligibility_status`` defaults to ``"unknown"``, which must never silently activate relief downstream. A globally diversified portfolio can contain both qualifying and non-qualifying assets, so eligibility is recorded as fractions rather than a single domestic/foreign flag.
      - `schema_version` integer
      - `eligibility_status` 'unknown' | 'eligible' | 'ineligible'
      - `qualifying_investment_income_fraction` number
      - `qualifying_capital_gains_fraction` number
      - `us_source_or_asset_qualification_confirmed` boolean
      - `beneficial_ownership_confirmed` boolean
      - `us_reporting_compliance_confirmed` boolean
    - `puma_csm` PumaCsmAssumptionPayload: France PUMa cotisation subsidiaire maladie (CSM) eligibility assumption. A scenario-level state, separate from the per-adult ``puma_household_adults`` entries. ``"unknown"`` (the default) must never silently activate CSM liability downstream.
      - `eligibility_status` 'unknown' | 'eligible' | 'ineligible'
    - `puma_household_adults` PumaHouseholdAdultPayload[]
      - `household_relationship` 'self' | 'spouse' | 'pacs_partner' | 'other_adult'
      - `income_attribution` 'per_adult' | 'equal_household'
      - `person` 'A' | 'B'
      - `coverage_start_date` string, date
      - `coverage_end_date` string, date
      - `item_id` string, uuid
  - `simulation_mode` 'monte_carlo_regime' | 'monte_carlo_iid' | 'historical'
  - `cohorts` CohortResult[]
    - `start_year` integer, required
    - `is_failure` boolean, required
    - `depletion_year_index` integer
    - `yearly` YearDetailSchema[]
      - `year` integer, required
      - `starting_balance` number, required
      - `starting_balances` YearDetailAccountBalances, required
        - `taxable` number, required
        - `tax_deferred` number, required
        - `tax_free` number, required
      - `portfolio_return_rate` number, required
      - `portfolio_return_amount` number, required
      - `income` YearDetailIncomeItem[], required
        - `source` string, required
        - `amount` number, required
      - `income_total` number, required
      - `expenses` YearDetailExpenseItem[], required
        - `description` string, required
        - `amount` number, required
      - `expenses_total` number, required
      - `investment_yield` number, required
      - `withdrawals` YearDetailWithdrawals, required
        - `taxable` number, required
        - `tax_deferred` number, required
        - `tax_free` number, required
        - `total` number, required
        - `taxable_principal` number
        - `taxable_yield_consumed` number
      - `capital_gains_realized` number, required
      - `taxes` YearDetailTaxes, required
        - `income` number, required
        - `investment_income` number, required
        - `capital_gains` number, required
        - `wealth` number, required
        - `surcharges` number, required
        - `total` number, required
        - `dual_tax` YearDetailDualTax
          - `residency_tax` number, required
          - `citizenship_tax` number, required
          - `ftc_applied` number, required
          - `net_tax` number, required
          - `residency_income_tax` number
          - `residency_investment_income_tax` number
          - `residency_capital_gains_tax` number
          - `residency_wealth_tax` number
          - `residency_surcharges` number
          - `citizenship_income_tax` number
          - `citizenship_investment_income_tax` number
          - `citizenship_capital_gains_tax` number
          - `citizenship_wealth_tax` number
          - `citizenship_surcharges` number
          - `gross_french_pfu_by_component` object
          - `article24_credit_by_component` object
          - `net_french_portfolio_tax` number
          - `article24_eligibility_status` string
        - `early_withdrawal_penalty` number
        - `englobamento` YearDetailEnglobamento: Per-year disclosure of Portugal's englobamento decision. ``selection`` is the user's preference (``"auto"``, ``"always"``, or ``"never"``); ``applied`` is which option the policy actually used — ``"englobamento"`` (progressive aggregation) or ``"autonomous"`` (28% flat). The two ``*_tax`` fields are the alternatives so the year detail can show what the other choice would have cost.
          - `selection` 'auto' | 'always' | 'never', required
          - `applied` 'englobamento' | 'autonomous', required
          - `autonomous_tax` number, required
          - `englobamento_tax` number, required
        - `reconciliation` YearDetailTaxReconciliation: Country-specific reconciliation surfaced for Canada (issue #662) so the year detail can show the dividend gross-up, dividend tax credits, capital-gains inclusion, and ordinary interest treatment alongside the headline tax.
          - `country` string, required
          - `yield_treatment` string, required
          - `yield_treatment_label` string, required
          - `used_default_assumption` boolean, required
          - `lines` YearDetailTaxReconciliationLine[], required
            - `key` string, required
            - `label` string, required
            - `amount` number, required
        - `taxable_securities_gross_proceeds` number
        - `proceeds_based_tax` number
        - `applied_simplifications` string[]
      - `surplus_deposited` number, required
      - `ending_balance` number, required
      - `ending_balances` YearDetailAccountBalances, required
        - `taxable` number, required
        - `tax_deferred` number, required
        - `tax_free` number, required
      - `cumulative_inflation` number, required
      - `asset_returns` AssetReturnItemSchema[]
        - `asset_class_id` string, required
        - `label` string, required
        - `weight` number, required
        - `return_rate` number, required
        - `return_amount` number, required
      - `rebalancing` RebalancingTradeSchema[]
        - `asset_class_id` string, required
        - `name` string, required
        - `pre_weight` number, required
        - `target_weight` number, required
        - `trade_amount` number, required
      - `withdrawal_strategy_name` string
      - `withdrawal_target` number
      - `withdrawal_floor` number
      - `withdrawal_ceiling` number
      - `withdrawal_baseline` number
      - `guardrail_hit` string
      - `regime` string
      - `exit_tax` YearDetailExitTaxLine: Exit-tax line for years where a relocation triggers an expatriation / deemed-disposition charge. Populated by the per-country exit-tax engine (see ``specs/exit-tax-engine.md``); omitted when no exit tax fires.
        - `amount` number, required
        - `description` string
      - `expense_change` YearDetailExpenseChange: Year where the active baseline expense changes (baseline-adjustment event or a relocation expense adjustment fires). Carries the prior and new amounts so the ledger can render a narrative change line.
        - `reason` string, required
        - `prior_amount` number, required
        - `new_amount` number, required
      - `rmds` YearDetailRmdLine[]
        - `amount` number, required
        - `account` string
        - `age` integer
        - `account_label` string
        - `wrapper_id` string
        - `bucket` string
        - `access_reason` string
      - `roth_conversions` YearDetailRothConversion[]
        - `amount` number, required
        - `description` string
        - `account_label` string
        - `wrapper_id` string
        - `bucket` string
        - `access_reason` string
      - `withdrawal_sources` YearDetailAccountSourceLine[]
        - `account_label` string
        - `wrapper_id` string
        - `bucket` string, required
        - `amount` number, required
        - `access_reason` string
      - `early_withdrawal_penalty` YearDetailEarlyWithdrawalPenaltyLine: US 10% additional federal tax on pre-59½ tax-deferred distributions. See ``specs/us-early-distribution-penalty.md``.
        - `amount` number, required
      - `harvested_loss` number
      - `puma_csm` YearDetailPumaCsm
        - `eligibility_status` string, required
        - `amount` number, required
        - `adults` YearDetailPumaCsmAdult[]
          - `person` string
          - `relationship` string, required
          - `liable` boolean, required
          - `reason` string, required
          - `assessment_income` number, required
          - `professional_income` number, required
          - `coverage_fraction` number, required
          - `amount` number, required
  - `cohort_index` CohortIndexEntry[]
    - `start_year` integer, required
    - `scenario_index` integer
    - `is_failure` boolean, required
    - `depletion_year_index` integer
    - `yearly_ending_balances` number[]
  - `warnings` HistoricalWarning[]
    - `type` 'historical_data_limited', required
    - `message` string, required
    - `earliest_start_year` integer, required
    - `latest_year` integer, required
    - `limiting_asset_class_ids` string[]
    - `limiting_asset_labels` string[]
  - `sweep` SweepResponse
    - `sweep_variable` 'retirement_year' | 'annual_savings' | 'portfolio_preset' | 'baseline_annual_expenses' | 'initial_net_worth' | 'country' | 'claim_age' | 'bond_tilt_pp', required
    - `current_value` union
      - number
      - string
    - `points` SweepPoint[], required
      - `variable_value` union, required
        - number
        - string
      - `label` string, required
      - `probability_of_success` number, required
      - `stats` object
      - `simulation_currency` string
      - `currency_conversion` object
      - `applied_assumptions` object
      - `annual_amount` number
      - `france_assumption_resolution` FranceAssumptionResolution
        - `context` 'personal_scenario' | 'country_sweep' | 'published_benchmark', required
        - `resolver_version` string, required
        - `benchmark_pack_id` string
        - `benchmark_pack_version` integer
        - `primary` FranceVariantReceipt, required: One resolved France run variant (primary or sensitivity).
          - `resolver_version` string, required
          - `context` 'personal_scenario' | 'country_sweep' | 'published_benchmark', required
          - `variant` string, required
          - `label` string, required
          - `benchmark_pack_id` string
          - `benchmark_pack_version` integer
          - `effective_assumptions` FranceEffectiveAssumption[]
            - `name` string, required
            - `value` unknown
            - `provenance` 'user_confirmed' | 'unresolved' | 'modeled_estimate' | 'benchmark_pack' | 'benchmark_sensitivity', required
            - `reason` string, required
          - `tax_policy_version` string
          - `tax_policy_sources` string[]
        - `sensitivities` FranceVariantReceipt[]
          - `resolver_version` string, required
          - `context` 'personal_scenario' | 'country_sweep' | 'published_benchmark', required
          - `variant` string, required
          - `label` string, required
          - `benchmark_pack_id` string
          - `benchmark_pack_version` integer
          - `effective_assumptions` FranceEffectiveAssumption[]
            - `name` string, required
            - `value` unknown
            - `provenance` 'user_confirmed' | 'unresolved' | 'modeled_estimate' | 'benchmark_pack' | 'benchmark_sensitivity', required
            - `reason` string, required
          - `tax_policy_version` string
          - `tax_policy_sources` string[]
      - `sensitivity_outcomes` FranceSensitivityOutcome[]
        - `variant` string, required
        - `label` string, required
        - `probability_of_success` number
        - `stats` FranceSensitivityStats: Compact slice of a sensitivity variant's run stats. The worker stores the sensitivity's *full* stats blob; only the handful of aggregate figures the UI renders side by side are re-serialized here so a France run response does not carry a second copy of every sampled timeline.
          - `probability_of_failure` number
          - `success_probability` number
          - `net_worth` PercentileStats
            - `average` number
            - `p10` number
            - `p50` number
            - `p90` number
          - `ending_portfolio` PercentileStats
            - `average` number
            - `p10` number
            - `p50` number
            - `p90` number
          - `tax_rates` PercentileStats
            - `average` number
            - `p10` number
            - `p50` number
            - `p90` number
        - `assumptions` FranceVariantReceipt: One resolved France run variant (primary or sensitivity).
          - `resolver_version` string, required
          - `context` 'personal_scenario' | 'country_sweep' | 'published_benchmark', required
          - `variant` string, required
          - `label` string, required
          - `benchmark_pack_id` string
          - `benchmark_pack_version` integer
          - `effective_assumptions` FranceEffectiveAssumption[]
            - `name` string, required
            - `value` unknown
            - `provenance` 'user_confirmed' | 'unresolved' | 'modeled_estimate' | 'benchmark_pack' | 'benchmark_sensitivity', required
            - `reason` string, required
          - `tax_policy_version` string
          - `tax_policy_sources` string[]
    - `disclaimer` string
  - `grid` GridResult
    - `grid_points` GridPoint[], required
      - `retirement_year` integer, required
      - `spending` number, required
      - `savings` number, required
      - `success_rate` number, required
    - `bounds` GridBounds, required
      - `retirement_year` number[], required
      - `spending` number[], required
      - `savings` number[], required
    - `base_config_hash` string, required
    - `computed_at` string, required
  - `roth_explorer` RothConversionExplorerResponse
    - `baseline` RothExplorerScenarioResult, required
      - `id` string, required
      - `label` string, required
      - `probability_of_success` number, required
      - `ending_balance` number
      - `total_tax` number
      - `per_year` RothExplorerYearDetail[]
        - `year` integer, required
        - `roth_conversion` number, required
        - `cumulative_converted` number, required
        - `ending_balance_delta` number
        - `tax_delta` number
    - `candidates` RothExplorerCandidate[], required
      - `id` string, required
      - `request_id` string, required
      - `label` string, required
      - `strategy` 'stay_below_income_cap' | 'fill_tax_bracket', required
      - `from_wrapper_id` string, required
      - `to_wrapper_id` string, required
      - `roth_conversions` RothConversionPayload[], required
        - `year` integer, required
        - `amount` number, required
        - `from_wrapper_id` string, required
        - `to_wrapper_id` string, required
        - `item_id` string, uuid
        - `derived_from` string
      - `total_converted` number, required
      - `tax_delta` number
      - `ending_balance_delta` number
      - `probability_of_success_delta` number, required
      - `per_year` RothExplorerYearDetail[]
        - `year` integer, required
        - `roth_conversion` number, required
        - `cumulative_converted` number, required
        - `ending_balance_delta` number
        - `tax_delta` number
    - `assumptions` string[]
    - `warnings` string[]
  - `error` RunError
    - `type` string
    - `message` string, required
    - `details` object
  - `runs_remaining` integer
  - `run_limit` RunLimit: Structured run-allowance summary returned for every user. ``scope`` selects which allowance applies: - ``anonymous_lifetime``: anonymous lifetime sign-in run cap. - ``member_daily``: signed-in free member's daily compute allowance. - ``unlimited``: Pro/pass users have no compute cap (limit/used/remaining and reset_at are all null). ``reset_at`` is the exact instant the daily window rolls over (member_daily only) so the frontend can render it in the user's local timezone.
    - `scope` 'anonymous_lifetime' | 'member_daily' | 'unlimited', required
    - `limit` integer
    - `used` integer
    - `remaining` integer
    - `reset_at` string, date-time
  - `progress` RunProgress
    - `current` integer, required
    - `total` integer, required
  - `requested_at` string, date-time
  - `compute_provenance` ComputeProvenanceResponse
    - `contract_version` integer, required
    - `available` boolean, required
    - `state` 'legacy'
    - `result_origin` 'computed' | 'copied'
    - `api_deploy_sha` string
    - `worker_deploy_sha` string
    - `engine_contract_version` string
    - `aggregate_result_schema_version` integer
    - `artifact_materializer_version` string
    - `artifact_schema_versions` object
    - `python_version` string
    - `numpy_version` string
    - `platform` string
    - `architecture` string
    - `run_kind` string
    - `worker_attempt_number` integer
  - `assumptions_version` string
  - `assumptions_version_state` 'declared' | 'legacy_unverified'
  - `source_assumptions_version` string
  - `source_assumptions_version_state` 'declared' | 'legacy_unverified'

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/retirementlab/apis/income-simulator-api.md) · [All operations](https://skmtc.dev/retirementlab/apis/income-simulator-api/llms.txt) · [OpenAPI document](https://skmtc.dev/retirementlab/apis/income-simulator-api/revisions/b0f00a0333fd?raw)
