openapi: 3.1.0
info:
  title: everinvest API
  description: Portfolio backtesting 1871-2024. NOT financial advice.
  version: 2.0.0
servers:
  - url: https://api.everinvest.ai

paths:
  /backtest:
    post:
      operationId: runBacktest
      x-openai-isConsequential: false
      summary: Run historical portfolio backtest
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                portfolios:
                  type: string
                  description: "REQUIRED JSON string. Simple: '{\"stocks.usa\":0.6,\"bonds.usa.gov.long\":0.4}'. Array: '[{\"name\":\"A\",\"allocation\":{...}},{\"name\":\"B\",\"allocation\":{...}}]'. Bond strategy: '{\"bonds.usa\":{\"weight\":0.4,\"strategy\":\"ladder\"}}'. Per-asset preferences: '{\"stocks.dev\":{\"weight\":0.5,\"accumulating\":false,\"currency\":\"USD\"},\"bonds.deu.gov.long\":{\"weight\":0.3},\"real.gold\":0.2}'. accumulating overrides global prefer_accumulating; currency picks best listing. Relocation: put portfolio_adjustments INSIDE portfolio: '[{\"allocation\":{...},\"portfolio_adjustments\":[{\"trigger\":{\"type\":\"year\",\"value\":10},\"new_country\":\"CYP\",\"new_tax_regime\":\"non_dom\",\"exit_tax_overrides\":{\"enabled\":false}}]}]'. exit_tax_overrides goes INSIDE the adjustment, NOT in tax_config."
                start_year:
                  type: integer
                  default: 1971
                  description: "Default 1971 (modern era). Use 1871 ONLY if user explicitly requests longer history."
                end_year:
                  type: integer
                  default: 2024
                window_years:
                  type: integer
                  default: 30
                investor_country:
                  type: string
                  default: USA
                  description: "USA, DEU, GBR, CHE, CYP, FRA, NLD, IRL, AUT, ESP, ITA, PRT, GRC, JPN, CAN, AUS, SGP, HKG, ARE, ARM"
                initial_capital:
                  type: number
                  default: 10000
                initial_capital_currency:
                  type: string
                prefer_accumulating:
                  type: boolean
                  default: true
                currency_basket:
                  type: string
                  description: "JSON string. Example: '{\"USA\":0.25,\"DEU\":0.25,\"CHE\":0.25,\"GBR\":0.25}'"
                rebalance_config:
                  type: string
                  description: "JSON string. Example: '{\"frequency_years\":1}'"
                withdrawal_config:
                  type: string
                  description: "JSON string. Example: '{\"withdrawal_strategy\":\"vpw\",\"start_trigger\":{\"type\":\"age\",\"value\":65},\"accumulation_start_age\":30}'"
                tax_config:
                  type: string
                  description: "JSON string. Can be global OR per-portfolio. When 'enabled':true, tax drag + income_tracking auto-enabled. 'apply_tax_drag' defaults to true (set false for report-only). Region aliases: 'state' (US), 'canton' (CH), 'province' (CA). CYP defaults to non_dom (2.65% GHS); use 'regime':'domiciled' for SDC+GHS. MLT non_dom has €5k/yr minimum tax (set other_income to offset). NLD Box 3: add 'box3':{'model':'kerstarrest'|'actual_returns'|'deemed_only'} — default kerstarrest (current), actual_returns = proposed 2028+ unrealized gains tax. Example: '{\"enabled\":true,\"account_type\":\"taxable\",\"state\":\"CA\",\"other_income\":60000}'."
                income_tracking:
                  type: boolean
                  default: false
                cash_flows:
                  type: string
                  description: "JSON string array. Example: '[{\"amount\":20000,\"recurring\":true,\"deployment\":\"monthly\",\"name\":\"DCA\"}]'"
                scenarios:
                  type: string
                  description: "JSON string array. Named: '[\"1970s_inflation\"]'. Custom: '[{\"name\":\"bear\",\"years\":10,\"assets\":{\"stocks.usa\":{\"mean\":-3,\"vol\":15},\"bonds.usa.gov.long\":1}}]'. Values in PERCENTAGE. Assets MUST be under 'assets' key."
                scenario_years:
                  type: integer
                  default: 10
                detailed:
                  type: boolean
                  default: false
                inflation_override:
                  type: string
                  description: "JSON string. Values in DECIMAL (0.03 = 3%). Formats: number '0.03', or '{\"default\":0.03,\"2022\":0.145}', or index '{\"type\":\"index\",\"2020\":100,\"2021\":105}'."
                inflation_country:
                  type: string
                  description: "ISO country code (DEU, USA, GBR, CHE, etc.). Use ANOTHER country's historical inflation instead of investor_country's. E.g., investor_country=USA + inflation_country=DEU → USD returns adjusted by German CPI. With currency_basket: applies same inflation to ALL basket perspectives (instead of each using its own). Useful for nomads/expats who earn in one currency but track purchasing power in another."
                fx_override:
                  type: string
                  description: "JSON string. Values in DECIMAL (-0.05 = 5% depreciation/yr). Example: '{\"PLN\":{\"default\":-0.03,\"2022\":-0.15}}'. Or actual rates: '{\"PLN\":{\"type\":\"rate\",\"2020\":4.0}}'."
                asset_overrides:
                  type: string
                  description: "JSON string. Define custom/new assets OR override existing ones. Values in PERCENTAGE (18.4 = 18.4%, not 0.184). WARNING: Custom assets like 'my.crypto' are NOT built-in — they ONLY work if you define them here with return data. Example: '{\"my.crypto\":{\"total_return\":{\"2015\":35,\"2016\":125,\"2017\":1300,\"2018\":-73,\"2019\":95,\"2020\":305,\"2021\":60,\"2022\":-64,\"2023\":155,\"2024\":120},\"dividend_yield\":0}}'. Key MUST be 'total_return' with year keys. Override existing: '{\"stocks.usa\":{\"total_return\":{\"2024\":18.4}}}'."
                starting_yields:
                  type: string
                random_seed:
                  type: integer
                  default: 42
                cost_basis_method:
                  type: string
                  enum: [fifo, average]
                family_config:
                  type: string
                fallback_overrides:
                  type: string
                  description: "JSON array. GLOBAL (applies to ALL portfolios). To compare with/without, run TWO separate backtests. Example: '[{\"asset\":\"real.gold\",\"fallback_to\":\"bonds.usa.gov.long\",\"start_year\":1871,\"end_year\":1968}]'. Set fallback_to to '' to disable."
                fee_config:
                  type: string
                  description: "JSON string. GLOBAL fee/cost defaults. Keys: 'broker' (preset: 'IBKR_tiered','IBKR_fixed','Swissquote','Scalable','Trade_Republic','Degiro','Fidelity','Schwab','Hargreaves','Vanguard_UK'), 'apply_ter' (bool, default true), 'apply_trading_fees' (bool), 'apply_custody_fees' (bool), 'ter_overrides' ({asset_id: pct}). Can also be set PER-PORTFOLIO inside each portfolio object as 'fee_config' key -- per-portfolio overrides global. Example global: '{\"broker\":\"IBKR_tiered\",\"apply_trading_fees\":true}'. Per-portfolio: portfolios='[{\"name\":\"No fees\",\"allocation\":{...}},{\"name\":\"IBKR\",\"allocation\":{...},\"fee_config\":{\"broker\":\"IBKR_tiered\",\"apply_trading_fees\":true}}]'."
                professional_investor:
                  type: boolean
                  default: false
                  description: "Set true for EU/EEA/UK professional investors to see US-domiciled ETF alternatives (normally hidden due to PRIIPs regulation)."
      responses:
        '200':
          description: Backtest results
          content:
            application/json:
              schema:
                type: object
                properties:
                  portfolios:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                  test_parameters:
                    type: object
                    additionalProperties: true

  /assets:
    get:
      operationId: listAssetCategories
      summary: List available assets with ETF tickers
      parameters:
        - name: investor_country
          in: query
          schema:
            type: string
            default: USA
        - name: include_all_assets
          in: query
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Asset categories
          content:
            application/json:
              schema:
                type: object
                properties:
                  categories:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                additionalProperties: true

  /resources/{resource_name}:
    get:
      operationId: getResource
      summary: Get documentation
      parameters:
        - name: resource_name
          in: path
          required: true
          schema:
            type: string
            enum: [guide, examples, assets, scenarios, taxation, account-types]
      responses:
        '200':
          description: Resource content
          content:
            application/json:
              schema:
                type: object
                properties:
                  content:
                    type: string
                additionalProperties: true
