---
title: "Preview Conversion Tax"
method: POST
path: "/api/scenarios/preview-conversion-tax"
tags: ["scenarios"]
---

# Preview Conversion Tax

`POST /api/scenarios/preview-conversion-tax`

## 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/scenarios/preview-conversion-tax`
- Auth: none declared

Single-year tax preview for a Roth conversion event.

Computes the marginal tax cost of adding ``amount`` to the conversion
year's taxable ordinary income, using the residency/regime that applies
in that year (honoring any relocation events). The baseline reflects the
scenario's projected ordinary income (wages, pensions, savings during
accumulation) so the preview captures the conversion's marginal bracket
impact rather than a stand-alone calculation. The conversion amount is
routed as residual ordinary income (matching the engine's
``tax_deferred_withdrawal`` lane), which carries the foreign-source tag
that the Portugal NHR exemption requires for US citizens abroad.

## Request body

- ConversionTaxPreviewRequest
  - `simulation` SimulationConfigPayload, required: 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
  - `year` integer, required
  - `amount` number, required

## Response `200`

Successful Response

- ConversionTaxPreviewResponse
  - `year` integer, required
  - `amount` number, required
  - `residency_country` string, required
  - `residency_regime` string
  - `citizenship_country` string, required
  - `overlay_active` boolean, required
  - `residency_tax_impact` number, required
  - `citizenship_tax_impact` number, required
  - `ftc_applied` number, required
  - `total_tax_impact` number, required

## 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)
