---
title: "Project a multi-year Roth conversion strategy"
method: POST
path: "/v1/roth-conversion"
tags: ["Retirement"]
---

# Project a multi-year Roth conversion strategy

`POST /v1/roth-conversion`

## Request

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

Projects converting tax-deferred savings to a Roth account each year up to the top of a target ordinary-income bracket, against a no-conversion baseline. Reuses the tax and RMD engines. Assumptions (v1): real (today's) dollars with a real return and the chosen year's brackets held constant; conversion tax paid from the converted amount; RMDs begin at the SECURE 2.0 age, are taken before conversions, and are assumed spent (not reinvested). Calculation engine output, not tax advice. Deterministic.

## Request body

- RothConversionRequest
  - `tax_year` integer, required: Supported: 2025, 2026.
  - `filing_status` 'single' | 'mfj' | 'mfs' | 'hoh', required
  - `current_age` integer, required
  - `end_age` integer, required: Projection horizon (inclusive); must be > current_age.
  - `birth_year` integer: Optional; sets the SECURE 2.0 RMD beginning age.
  - `tax_deferred_balance` number, required
  - `roth_balance` number
  - `other_ordinary_income` number: Annual ordinary income besides RMDs/conversions (real).
  - `annual_return` number, required: Real annual return applied to both accounts.
  - `target_bracket` number, required: Marginal ordinary rate to fill each conversion year.
  - `conversion_end_age` integer: Stop converting at/before this age (exclusive). 0 = convert through end_age.
  - `state_tax_rate` number
  - `irmaa_tier_cap` integer: Optional. Cap each year's conversion so MAGI stays under the top of this Medicare IRMAA tier (0–5).
  - `pay_tax_from_external` boolean: Pay each year's conversion tax from a side taxable account instead of netting it from the conversion, so the full conversion lands in the Roth.
  - `external_taxable_balance` number: The side taxable account that funds the conversion tax. Required (> 0) when pay_tax_from_external is set.
  - `external_return` number: Real after-tax return on the external account; set below annual_return to model taxable drag. Defaults to annual_return.

## Response `200`

Conversion projection

- RothConversionResponse
  - `result_id` string
  - `engine_version` string
  - `inputs` RothConversionRequest
    - `tax_year` integer, required: Supported: 2025, 2026.
    - `filing_status` 'single' | 'mfj' | 'mfs' | 'hoh', required
    - `current_age` integer, required
    - `end_age` integer, required: Projection horizon (inclusive); must be > current_age.
    - `birth_year` integer: Optional; sets the SECURE 2.0 RMD beginning age.
    - `tax_deferred_balance` number, required
    - `roth_balance` number
    - `other_ordinary_income` number: Annual ordinary income besides RMDs/conversions (real).
    - `annual_return` number, required: Real annual return applied to both accounts.
    - `target_bracket` number, required: Marginal ordinary rate to fill each conversion year.
    - `conversion_end_age` integer: Stop converting at/before this age (exclusive). 0 = convert through end_age.
    - `state_tax_rate` number
    - `irmaa_tier_cap` integer: Optional. Cap each year's conversion so MAGI stays under the top of this Medicare IRMAA tier (0–5).
    - `pay_tax_from_external` boolean: Pay each year's conversion tax from a side taxable account instead of netting it from the conversion, so the full conversion lands in the Roth.
    - `external_taxable_balance` number: The side taxable account that funds the conversion tax. Required (> 0) when pay_tax_from_external is set.
    - `external_return` number: Real after-tax return on the external account; set below annual_return to model taxable drag. Defaults to annual_return.
  - `result` object
    - `schedule` object[]: Per-year projection of the conversion path.
      - `age` integer
      - `other_ordinary_income` number
      - `rmd` number
      - `conversion` number: Gross amount moved out of tax-deferred.
      - `ordinary_income` number
      - `tax` number: Total tax for the year.
      - `conversion_tax` number: Incremental tax caused by the conversion.
      - `marginal_rate` number
      - `irmaa_tier` integer: Medicare IRMAA tier this year (0 if pre-Medicare).
      - `irmaa_surcharge` number: Annual IRMAA surcharge this year (2-year MAGI lookback).
      - `tax_deferred_end` number
      - `roth_end` number
      - `external_taxable_end` number: Side taxable account year-end balance; present only with pay_tax_from_external.
    - `with_conversions` RothScenario
      - `total_converted` number
      - `total_tax` number: Cumulative income tax over the horizon.
      - `total_irmaa_surcharge` number: Cumulative Medicare IRMAA surcharges (2-year lookback).
      - `ending_tax_deferred` number: Pre-tax dollars.
      - `ending_roth` number: Tax-free dollars.
      - `ending_external_taxable` number: Side taxable account ending balance; present only with pay_tax_from_external.
      - `conversion_tax_paid_externally` number: Cumulative conversion tax drawn from the external account; present only with pay_tax_from_external.
      - `after_tax_ending_value` number: Roth + tax-deferred*(1 - final marginal rate) + external taxable - cumulative IRMAA.
    - `baseline` RothScenario
      - `total_converted` number
      - `total_tax` number: Cumulative income tax over the horizon.
      - `total_irmaa_surcharge` number: Cumulative Medicare IRMAA surcharges (2-year lookback).
      - `ending_tax_deferred` number: Pre-tax dollars.
      - `ending_roth` number: Tax-free dollars.
      - `ending_external_taxable` number: Side taxable account ending balance; present only with pay_tax_from_external.
      - `conversion_tax_paid_externally` number: Cumulative conversion tax drawn from the external account; present only with pay_tax_from_external.
      - `after_tax_ending_value` number: Roth + tax-deferred*(1 - final marginal rate) + external taxable - cumulative IRMAA.
    - `after_tax_value_gain` number: with_conversions.after_tax_ending_value minus baseline's; positive favors converting (illustrative, terminal-rate basis).
  - `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)
