{
  "openapi": "3.1.1",
  "info": {
    "title": "Money-saving & comparison API",
    "version": "1.0.0",
    "summary": "Deterministic tools for people and AI agents.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://money.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/tools": {
      "get": {
        "operationId": "listTools",
        "summary": "Catalog of all tools",
        "responses": {
          "200": {
            "description": "Tool list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{id}": {
      "get": {
        "operationId": "getTool",
        "summary": "Definition of one tool",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "bill-detective",
                "break-even-explorer",
                "bulk-buy-brain",
                "currency-converter",
                "hidden-fee-finder",
                "historical-fx-chart",
                "inflation-time-machine",
                "multi-currency-invoice-calculator",
                "price-per-everything",
                "salary-reality-check",
                "subscription-stack",
                "travel-exchange-fee-calculator",
                "warranty-value-calculator"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/bill-detective/calculate": {
      "post": {
        "operationId": "calculateBillDetective",
        "summary": "Spot spikes, price changes and calculation errors across your utility bills.",
        "description": "Enter several billing periods of an electricity, gas, water or data bill. Bill Detective normalises consumption per day, flags unusual periods (outliers), unit-price and standing-charge changes, and totals that do not add up, each with a plain-English explanation.\n\nMethodology:\n- Days = end date − start date + 1 (both dates count). Consumption per day = consumption ÷ days. Periods are sorted by start date.\n- Computed total = consumption × unit price + fixed charge + other charges. A mismatch is flagged when |total billed − computed| > tolerance % × computed (minimum 0.01).\n- Consumption change: per-day consumption vs. the previous period; flagged when the absolute change exceeds the threshold (default 25%).\n- Above average: per-day consumption more than the threshold above the average of all periods.\n- Outliers (IQR rule, only with ≥ 4 periods): per-day value above Q3 + 1.5 × IQR or below Q1 − 1.5 × IQR, quartiles by linear interpolation.\n- Outliers (median rule): per-day value more than X% (default 30%) above the median per-day value.\n- Unit price change: any change vs. the previous period. Fixed charge change: both the fixed charge per period and the fixed charge per day differ from the previous period by more than 1% (so a flat monthly fee over months of different length, or a fixed daily rate, is not flagged).\n- Gaps and overlaps between consecutive periods are reported as information.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "bill_detective",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillDetectiveInput"
              },
              "examples": {
                "example1": {
                  "summary": "Monthly electricity with a winter spike and a price rise",
                  "value": {
                    "unit": "kWh",
                    "currency": "EUR",
                    "periods": [
                      {
                        "label": "Jan",
                        "startDate": "2026-01-01",
                        "endDate": "2026-01-31",
                        "consumption": 310,
                        "unitPrice": 0.32,
                        "fixedCharge": 12,
                        "totalBilled": 111.2
                      },
                      {
                        "label": "Feb",
                        "startDate": "2026-02-01",
                        "endDate": "2026-02-28",
                        "consumption": 280,
                        "unitPrice": 0.32,
                        "fixedCharge": 12,
                        "totalBilled": 101.6
                      },
                      {
                        "label": "Mar",
                        "startDate": "2026-03-01",
                        "endDate": "2026-03-31",
                        "consumption": 300,
                        "unitPrice": 0.32,
                        "fixedCharge": 12,
                        "totalBilled": 108
                      },
                      {
                        "label": "Apr",
                        "startDate": "2026-04-01",
                        "endDate": "2026-04-30",
                        "consumption": 290,
                        "unitPrice": 0.35,
                        "fixedCharge": 12,
                        "totalBilled": 113.5
                      },
                      {
                        "label": "May",
                        "startDate": "2026-05-01",
                        "endDate": "2026-05-31",
                        "consumption": 520,
                        "unitPrice": 0.35,
                        "fixedCharge": 14,
                        "totalBilled": 198
                      },
                      {
                        "label": "Jun",
                        "startDate": "2026-06-01",
                        "endDate": "2026-06-30",
                        "consumption": 295,
                        "unitPrice": 0.35,
                        "fixedCharge": 14,
                        "totalBilled": 117.25
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Quarterly water bills",
                  "value": {
                    "unit": "m3",
                    "currency": "GBP",
                    "periods": [
                      {
                        "label": "Q1",
                        "startDate": "2025-01-01",
                        "endDate": "2025-03-31",
                        "consumption": 24,
                        "unitPrice": 2.1,
                        "fixedCharge": 18,
                        "totalBilled": 68.4
                      },
                      {
                        "label": "Q2",
                        "startDate": "2025-04-01",
                        "endDate": "2025-06-30",
                        "consumption": 26,
                        "unitPrice": 2.1,
                        "fixedCharge": 18,
                        "totalBilled": 72.6
                      },
                      {
                        "label": "Q3",
                        "startDate": "2025-07-01",
                        "endDate": "2025-09-30",
                        "consumption": 31,
                        "unitPrice": 2.25,
                        "fixedCharge": 18,
                        "totalBilled": 87.75
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillDetectiveResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/break-even-explorer/calculate": {
      "post": {
        "operationId": "calculateBreakEvenExplorer",
        "summary": "When does an investment pay for itself? Payback, NPV and IRR for solar, EVs, heat pumps…",
        "description": "Enter an upfront investment, subsidies and the yearly savings it brings, with optional price escalation, performance degradation and maintenance. The explorer returns simple and discounted payback (fractional years), NPV, IRR and a year-by-year cumulative cash-flow table.\n\nMethodology:\n- Net investment (year 0 outflow) = upfront investment − subsidies.\n- Savings in year t = year-1 savings × (1 + escalation)^(t−1) × (1 − degradation)^(t−1), unless overridden for that year.\n- Net cash flow in year t = savings − maintenance (maintenance is constant in nominal terms).\n- Simple payback = the year in which cumulative cash flow turns non-negative, interpolated linearly within that year (savings assumed to accrue evenly).\n- Discounted payback uses cash flows discounted by (1 + r)^t; NPV = −net investment + Σ net cash flow ÷ (1 + r)^t.\n- IRR = the rate at which NPV = 0, found by bisection between −99% and 1000% (null if NPV does not change sign in that range).\n- Presets only supply defaults for escalation, degradation and horizon when those fields are left empty; the values used are always listed.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "break_even_explorer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BreakEvenExplorerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Rooftop solar with a grant",
                  "value": {
                    "preset": "solar",
                    "currency": "EUR",
                    "upfrontInvestment": 12000,
                    "subsidies": 1500,
                    "annualSavings": 1100,
                    "maintenancePerYear": 100,
                    "discountRatePercent": 3
                  }
                },
                "example2": {
                  "summary": "Insulation project, flat savings, 15 years",
                  "value": {
                    "preset": "custom",
                    "currency": "EUR",
                    "upfrontInvestment": 6000,
                    "subsidies": 0,
                    "annualSavings": 700,
                    "savingsEscalationPercent": 0,
                    "degradationPercent": 0,
                    "maintenancePerYear": 0,
                    "discountRatePercent": 4,
                    "horizonYears": 15
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BreakEvenExplorerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/bulk-buy-brain/calculate": {
      "post": {
        "operationId": "calculateBulkBuyBrain",
        "summary": "Is the bulk pack really cheaper once waste, storage and tied-up cash are counted?",
        "description": "Compare a small pack with a bulk pack using how fast you actually use the product, its shelf life, storage cost and the interest your money could earn. The tool computes the effective cost per unit you actually use, your saving or loss, and the largest bulk size worth buying.\n\nMethodology:\n- Small-pack unit price ps = small price ÷ small quantity (small packs bought as needed, no waste). Bulk unit price pb = bulk price ÷ bulk quantity.\n- Usable quantity = min(bulk quantity, consumption per week × shelf life); waste = bulk quantity − usable quantity.\n- Weeks held = usable quantity ÷ consumption per week. Storage cost = storage cost per week × weeks held.\n- Opportunity cost = bulk price × interest rate × (weeks held ÷ 52.18) ÷ 2 (the prepaid amount is drawn down linearly, so on average half is tied up).\n- Effective cost per used unit = (bulk price + storage + opportunity cost) ÷ usable quantity. Savings = ps × usable quantity − bulk total cost.\n- Max worthwhile size: the largest Q for which pb·Q + storage + opportunity cost ≤ ps × usable(Q), solved in closed form for the no-waste and the waste regime.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "bulk_buy_brain",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkBuyBrainInput"
              },
              "examples": {
                "example1": {
                  "summary": "Toilet paper: 8 rolls vs 48 rolls",
                  "value": {
                    "currency": "EUR",
                    "unitLabel": "rolls",
                    "smallPrice": 3.49,
                    "smallQuantity": 8,
                    "bulkPrice": 17.99,
                    "bulkQuantity": 48,
                    "consumptionPerWeek": 3,
                    "storageCostPerWeek": 0,
                    "interestRatePercent": 3
                  }
                },
                "example2": {
                  "summary": "Greek yoghurt: 500 g vs 2 kg tub (spoils)",
                  "value": {
                    "currency": "EUR",
                    "unitLabel": "g",
                    "smallPrice": 2.29,
                    "smallQuantity": 500,
                    "bulkPrice": 6.99,
                    "bulkQuantity": 2000,
                    "consumptionPerWeek": 600,
                    "shelfLifeWeeks": 2,
                    "storageCostPerWeek": 0,
                    "interestRatePercent": 3
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkBuyBrainResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/currency-converter/calculate": {
      "post": {
        "operationId": "calculateCurrencyConverter",
        "summary": "Convert an amount into several currencies at ECB euro reference rates, with rate date and source.",
        "description": "Converts an amount between the 30 currencies of the European Central Bank’s daily euro foreign exchange reference rates – into several target currencies at once, at the latest rates or those of a past date. Cross rates are calculated via the euro; every result shows the rate, the inverse rate, the reference date and the source.\n\nMethodology:\n- Rates come from the ECB’s daily reference-rate file (eurofxref-daily.xml) or, for past dates, from the ECB Data Portal (dataset EXR, daily series D.<currency>.EUR.SP00.A). They are cached per day; the daily file is re-checked at most hourly until the day’s rates (around 16:00 CET) are available.\n- All ECB rates are quoted as units of currency per 1 euro, so a cross rate is rate(to) ÷ rate(from); converting X from A to B = X × rate(B) ÷ rate(A). Rates are shown with 6 significant digits; amounts are rounded half away from zero to the currency’s minor units (0 for JPY, KRW and ISK).\n- For a date without a publication (weekends, TARGET holidays) the last earlier publication is used and named.\n- Reference rates are published for information only. Card schemes, banks and exchange offices use their own rates and add fees, so the amount you actually pay or receive will differ.\n\nECB reference rates are not transaction rates. For a real payment, check your provider’s rate and fees.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "currency_converter",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CurrencyConverterInput"
              },
              "examples": {
                "example1": {
                  "summary": "100 EUR into USD, GBP, CHF and JPY (latest)",
                  "value": {
                    "amount": 100,
                    "from": "EUR",
                    "to": "USD, GBP, CHF, JPY"
                  }
                },
                "example2": {
                  "summary": "250 USD into EUR and GBP on 2024-01-02",
                  "value": {
                    "amount": 250,
                    "from": "USD",
                    "to": "EUR, GBP",
                    "date": "2024-01-02"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrencyConverterResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/hidden-fee-finder/calculate": {
      "post": {
        "operationId": "calculateHiddenFeeFinder",
        "summary": "Paste a quote, contract or invoice and see every fee, tax, penalty and add-on.",
        "description": "Paste the lines of a quote, rental contract, booking or invoice (English or German). Hidden Fee Finder extracts the amounts, classifies each line with transparent keyword rules (base price, fee, tax, surcharge, penalty, deposit, discount, insurance/add-on, recurring), flags conditional penalties and optional add-ons, and shows how much of the total is not the base price.\n\nMethodology:\n- Each non-empty line is scanned for amounts. A number counts as money if it has a currency marker (€, $, £, EUR, USD, GBP, CHF…), exactly two decimals, or ends the line; the last such amount on a line is used. A number followed by % is a percentage.\n- Number formats: with both separators the last one is the decimal mark; \"1,234\" (comma + 3 digits) is read as a thousands separator in auto mode, \"12,50\" as a decimal; choose a fixed format to override.\n- Categories are assigned by ordered keyword rules (total → penalty → deposit → discount → tax → insurance/add-on → surcharge → fee → recurring → base). The first matching keyword is shown. Lines without any keyword default to base price.\n- Flags: conditional (late, cancellation, early termination, if, per day, wenn, falls, Verzug, pro Tag…), recurring (monthly, per year, subscription, monatlich…), optional/hidden (optional, pre-selected, recommended, auto-renew, empfohlen, voreingestellt…), included tax (incl./inkl./included).\n- Payable total = base + fees + taxes + surcharges + insurance/add-ons + recurring + discounts. Excluded: deposits (refundable), conditional lines (only if triggered), total lines, and taxes marked as included.\n- Percent-only lines: taxes are applied to the sum of all other included lines; other percentages are applied to the base total.\n- Non-base share = (payable total − base − discounts) ÷ payable total.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "hidden_fee_finder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HiddenFeeFinderInput"
              },
              "examples": {
                "example1": {
                  "summary": "Hotel booking with resort fee and conditional charges",
                  "value": {
                    "currency": "USD",
                    "text": "Room rate, 3 nights: $420.00\nResort fee (mandatory): $105.00\nCity tax: 6%\nTravel insurance (pre-selected, optional): $29.99\nLate checkout fee if after 12:00: $50.00\nSecurity deposit (refundable): $200.00\nMember discount: -$21.00\nTotal: $566.03"
                  }
                },
                "example2": {
                  "summary": "German rental quote",
                  "value": {
                    "currency": "EUR",
                    "numberFormat": "de",
                    "text": "Grundpreis Miete Transporter 1 Tag 89,00 €\nBearbeitungsgebühr 15,00 €\nWochenendzuschlag 20,00 €\nVollkaskoversicherung optional 24,90 €\nKaution 300,00 €\nVertragsstrafe bei verspäteter Rückgabe pro Tag 75,00 €\nGesamtbetrag inkl. 19% MwSt. 148,90 €"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HiddenFeeFinderResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/historical-fx-chart/calculate": {
      "post": {
        "operationId": "calculateHistoricalFxChart",
        "summary": "Chart exchange rates over time from ECB data: min, max, average, change, volatility and CSV export.",
        "description": "Pick a base currency, one or more quote currencies and a date range (back to 1999). The tool loads the ECB’s daily euro reference rates, builds the cross rates, draws a line chart (indexed to 100 to compare several pairs) and reports start, end, minimum, maximum, average, change and annualised volatility, with a CSV download.\n\nMethodology:\n- Daily reference rates (units per euro) are loaded from the ECB Data Portal SDMX API as CSV (series D.<currency>.EUR.SP00.A, only the requested currencies and dates) and cached; ranges ending more than a week ago are cached permanently, recent ranges per day.\n- Cross rate base/quote on each publication day = rate(quote) ÷ rate(base); days where either leg is missing are skipped. EUR legs are 1.\n- Statistics: first and last observation, minimum and maximum with dates, arithmetic mean, change = last ÷ first − 1, annualised volatility = standard deviation of daily log returns × √252.\n- Indexed view: every pair divided by its first value × 100, so pairs with very different levels (e.g. JPY and GBP) can be compared.\n\nReference rates for information; they are not the rates at which you could have traded.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "historical_fx_chart",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HistoricalFxChartInput"
              },
              "examples": {
                "example1": {
                  "summary": "EUR/USD and EUR/GBP over 2024 (indexed)",
                  "value": {
                    "base": "EUR",
                    "quotes": "USD, GBP",
                    "start": "2024-01-01",
                    "end": "2024-12-31"
                  }
                },
                "example2": {
                  "summary": "USD/JPY in 2023",
                  "value": {
                    "base": "USD",
                    "quotes": "JPY",
                    "start": "2023-01-01",
                    "end": "2023-12-31",
                    "png": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoricalFxChartResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/inflation-time-machine/calculate": {
      "post": {
        "operationId": "calculateInflationTimeMachine",
        "summary": "What was money from one year worth in another? CPI-based, 20 countries, World Bank data.",
        "description": "Convert an amount between years with the consumer price index (2010 = 100) published by the World Bank. Shows the equivalent value, cumulative inflation, average annual inflation and lost purchasing power, with a year-by-year table.\n\nMethodology:\n- Data: World Bank WDI indicator FP.CPI.TOTL, consumer price index (2010 = 100), annual averages, snapshot stored with retrieval date.\n- Equivalent value = amount × CPI(to year) ÷ CPI(from year).\n- Cumulative inflation = (CPI(later) ÷ CPI(earlier) − 1) × 100.\n- Average annual inflation (CAGR) = ((CPI(later) ÷ CPI(earlier))^(1 ÷ years) − 1) × 100.\n- Purchasing power loss = (1 − CPI(earlier) ÷ CPI(later)) × 100: how much less the same nominal amount buys in the later year.\n- Because CPI values are annual averages, results compare average price levels of whole years, not specific dates.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "inflation_time_machine",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InflationTimeMachineInput"
              },
              "examples": {
                "example1": {
                  "summary": "100 EUR in Germany, 2000 → 2024",
                  "value": {
                    "amount": 100,
                    "currency": "EUR",
                    "fromYear": 2000,
                    "toYear": 2024,
                    "country": "DE"
                  }
                },
                "example2": {
                  "summary": "1,000 USD in the US, 1980 → 2024",
                  "value": {
                    "amount": 1000,
                    "currency": "USD",
                    "fromYear": 1980,
                    "toYear": 2024,
                    "country": "US"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InflationTimeMachineResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/multi-currency-invoice-calculator/calculate": {
      "post": {
        "operationId": "calculateMultiCurrencyInvoiceCalculator",
        "summary": "Convert invoice lines in different currencies at ECB reference rates, with VAT, rounding and totals.",
        "description": "Enter invoice lines in different currencies, the invoice currency and a rate date. The calculator converts every line at the ECB euro reference rates of that date (or your own rates), applies VAT per line, shows the VAT summary and totals with either per-line or per-document rounding (and the difference between both), and writes the “converted at the ECB reference rate of …” statement. Download as CSV or an HTML table.\n\nMethodology:\n- Each line’s net amount = quantity × unit price, rounded to the minor units of its own currency, then converted with the cross rate rate(invoice currency) ÷ rate(line currency), both quoted per euro by the ECB.\n- Per-line rounding: each converted net amount and its VAT are rounded to the invoice currency’s minor units and then added up. Per-document rounding: exact converted amounts are added up per VAT rate, the net subtotal is rounded and VAT is calculated once per rate. Both totals are shown, because tax authorities and accounting systems differ in which they expect.\n- Rounding is half away from zero (commercial rounding). Lines without a currency use the invoice currency; lines without a VAT rate have 0 %.\n- ECB rates for a date are the last publication on or before it (none on weekends and TARGET holidays). Manual rates are entered per euro, exactly like the ECB quotes.\n\nNot tax advice. Check which exchange rate and rounding your tax authority requires for invoices and VAT returns.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "multi_currency_invoice_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MultiCurrencyInvoiceCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "EUR invoice with USD and GBP costs (manual ECB-style rates)",
                  "value": {
                    "invoiceCurrency": "EUR",
                    "rateSource": "manual",
                    "rateDate": "2026-10-16",
                    "manualRates": "USD 1.1253\nGBP 0.84385",
                    "lines": [
                      {
                        "description": "Consulting (days)",
                        "quantity": 3,
                        "unitPrice": 850,
                        "currency": "EUR",
                        "vatRate": 19
                      },
                      {
                        "description": "Cloud hosting",
                        "quantity": 1,
                        "unitPrice": 412.37,
                        "currency": "USD",
                        "vatRate": 19
                      },
                      {
                        "description": "Conference ticket",
                        "quantity": 2,
                        "unitPrice": 299.99,
                        "currency": "GBP",
                        "vatRate": 7
                      },
                      {
                        "description": "Travel expenses (no VAT)",
                        "quantity": 1,
                        "unitPrice": 123.45,
                        "currency": "USD",
                        "vatRate": 0
                      }
                    ],
                    "rounding": "line"
                  }
                },
                "example2": {
                  "summary": "CHF invoice, document rounding (cross rates via EUR)",
                  "value": {
                    "invoiceCurrency": "CHF",
                    "rateSource": "manual",
                    "manualRates": "CHF 0.9272\nUSD 1.1253\nJPY 177.82",
                    "rounding": "document",
                    "lines": [
                      {
                        "description": "Licences",
                        "quantity": 7,
                        "unitPrice": 19.99,
                        "currency": "USD",
                        "vatRate": 8.1
                      },
                      {
                        "description": "Support",
                        "quantity": 1,
                        "unitPrice": 15000,
                        "currency": "JPY",
                        "vatRate": 8.1
                      },
                      {
                        "description": "Setup",
                        "quantity": 1,
                        "unitPrice": 250,
                        "currency": "CHF",
                        "vatRate": 8.1
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MultiCurrencyInvoiceCalculatorResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/price-per-everything/calculate": {
      "post": {
        "operationId": "calculatePricePerEverything",
        "summary": "Compare products by price per gram, use, serving, hour or year of life.",
        "description": "Enter two or more products with their price and quantity (grams, uses, servings, hours or years). Price Per Everything normalises them to the same basis, accounts for waste, and ranks them from cheapest to most expensive per unit.\n\nMethodology:\n- Usable quantity = quantity × (1 − waste %).\n- Price per unit = price ÷ usable quantity, unrounded internally; displayed with 4 significant digits.\n- Products are ranked by price per unit; ties keep input order.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "price_per_everything",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PricePerEverythingInput"
              },
              "examples": {
                "example1": {
                  "summary": "Coffee: small vs. large bag",
                  "value": {
                    "basis": "unit",
                    "currency": "EUR",
                    "products": [
                      {
                        "name": "Coffee 250 g",
                        "price": 4.99,
                        "quantity": 250,
                        "unit": "g"
                      },
                      {
                        "name": "Coffee 1 kg",
                        "price": 15.49,
                        "quantity": 1000,
                        "unit": "g",
                        "wastePercent": 10
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Shoes: cost per wear",
                  "value": {
                    "basis": "use",
                    "currency": "USD",
                    "products": [
                      {
                        "name": "Budget sneakers",
                        "price": 40,
                        "quantity": 120
                      },
                      {
                        "name": "Leather boots",
                        "price": 180,
                        "quantity": 900
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PricePerEverythingResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/salary-reality-check/calculate": {
      "post": {
        "operationId": "calculateSalaryRealityCheck",
        "summary": "Compare job offers by real net pay per hour, including commute time and living costs.",
        "description": "Enter 2–4 job offers with salary, your own effective tax rate, bonus, benefits, commute and cost-of-living differences. The tool ranks them by net income per hour of working plus commuting time, so a higher salary with a long commute or an expensive city is put into perspective.\n\nMethodology:\n- Gross total = gross salary + annual bonus. Net annual = gross total × (1 − effective tax+social rate) + benefits value (benefits are treated as already net).\n- The effective tax+social rate is supplied by you; no national tax rules are applied.\n- Commute days per year = working days × (1 − remote days per week ÷ 5). Commute hours = commute days × 2 × one-way minutes ÷ 60.\n- Work hours per year = weekly hours ÷ 5 × working days (a 5-day week is assumed).\n- Net after costs = net annual − 12 × monthly commute cost − 12 × monthly living cost difference.\n- Net per hour incl. commute = net after costs ÷ (work hours + commute hours). Offers are ranked by this value; ties keep input order.\n- Value of commute time = commute hours × (net annual ÷ work hours).",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "salary_reality_check",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SalaryRealityCheckInput"
              },
              "examples": {
                "example1": {
                  "summary": "City job vs. remote-friendly job",
                  "value": {
                    "currency": "EUR",
                    "offers": [
                      {
                        "name": "Big-city bank",
                        "grossAnnual": 72000,
                        "taxRatePercent": 42,
                        "bonus": 6000,
                        "benefitsValue": 1200,
                        "commuteMinutesOneWay": 55,
                        "commuteCostMonthly": 90,
                        "workingDays": 220,
                        "weeklyHours": 42,
                        "livingCostDiffMonthly": 450,
                        "remoteDaysPerWeek": 1
                      },
                      {
                        "name": "Local software firm",
                        "grossAnnual": 62000,
                        "taxRatePercent": 39,
                        "bonus": 0,
                        "benefitsValue": 800,
                        "commuteMinutesOneWay": 20,
                        "commuteCostMonthly": 40,
                        "workingDays": 220,
                        "weeklyHours": 40,
                        "livingCostDiffMonthly": 0,
                        "remoteDaysPerWeek": 3
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Three offers with different hours",
                  "value": {
                    "currency": "USD",
                    "offers": [
                      {
                        "name": "Startup",
                        "grossAnnual": 95000,
                        "taxRatePercent": 30,
                        "benefitsValue": 3000,
                        "commuteMinutesOneWay": 30,
                        "commuteCostMonthly": 120,
                        "weeklyHours": 50
                      },
                      {
                        "name": "Corporate",
                        "grossAnnual": 105000,
                        "taxRatePercent": 32,
                        "bonus": 10000,
                        "benefitsValue": 8000,
                        "commuteMinutesOneWay": 60,
                        "commuteCostMonthly": 250,
                        "weeklyHours": 45
                      },
                      {
                        "name": "Fully remote",
                        "grossAnnual": 88000,
                        "taxRatePercent": 29,
                        "benefitsValue": 2000,
                        "remoteDaysPerWeek": 5,
                        "weeklyHours": 40
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SalaryRealityCheckResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/subscription-stack/calculate": {
      "post": {
        "operationId": "calculateSubscriptionStack",
        "summary": "Add up every subscription, spot overlaps and see which ones to cancel and what you save.",
        "description": "List your subscriptions with price, billing cycle, category, how often you use them and how many people share them. Subscription Stack converts everything to monthly and yearly totals, groups by category, flags overlapping services, computes cost per use and lists cancellation candidates with the savings.\n\nMethodology:\n- Monthly price = weekly × (365.25/12)/7 (≈ 4.348), monthly × 1, quarterly ÷ 3, yearly ÷ 12.\n- Your share = monthly price ÷ number of people sharing; annual = your monthly share × 12.\n- Cost per use = your monthly share ÷ uses per month (not computed when uses are unknown or zero).\n- Overlap = two or more subscriptions in the same category (excluding “other”).\n- Cancel candidate if uses per month < your threshold, or cost per use > your threshold, or it is the most expensive per use (or least used) service within an overlapping category.\n- Savings if cancelled = sum of your monthly shares of all candidates; annual = × 12.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "subscription_stack",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionStackInput"
              },
              "examples": {
                "example1": {
                  "summary": "Typical household stack",
                  "value": {
                    "currency": "EUR",
                    "minUsesPerMonth": 2,
                    "maxCostPerUse": 5,
                    "subscriptions": [
                      {
                        "name": "Netflix",
                        "price": 13.99,
                        "billing": "monthly",
                        "category": "video",
                        "usesPerMonth": 12,
                        "sharedBy": 2
                      },
                      {
                        "name": "Disney+",
                        "price": 89.9,
                        "billing": "yearly",
                        "category": "video",
                        "usesPerMonth": 2
                      },
                      {
                        "name": "Spotify",
                        "price": 10.99,
                        "billing": "monthly",
                        "category": "music",
                        "usesPerMonth": 40
                      },
                      {
                        "name": "Gym",
                        "price": 34.9,
                        "billing": "monthly",
                        "category": "fitness",
                        "usesPerMonth": 3
                      },
                      {
                        "name": "Cloud 2 TB",
                        "price": 9.99,
                        "billing": "monthly",
                        "category": "cloud-storage",
                        "usesPerMonth": 30
                      },
                      {
                        "name": "Newspaper",
                        "price": 29.97,
                        "billing": "quarterly",
                        "category": "news",
                        "usesPerMonth": 1
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Freelancer software stack",
                  "value": {
                    "currency": "USD",
                    "minUsesPerMonth": 4,
                    "maxCostPerUse": 3,
                    "subscriptions": [
                      {
                        "name": "Design suite",
                        "price": 59.99,
                        "billing": "monthly",
                        "category": "software",
                        "usesPerMonth": 20
                      },
                      {
                        "name": "Second design app",
                        "price": 99,
                        "billing": "yearly",
                        "category": "software",
                        "usesPerMonth": 1
                      },
                      {
                        "name": "Cloud backup",
                        "price": 6,
                        "billing": "monthly",
                        "category": "cloud-storage",
                        "usesPerMonth": 30
                      },
                      {
                        "name": "Extra cloud drive",
                        "price": 2.99,
                        "billing": "monthly",
                        "category": "cloud-storage"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionStackResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/travel-exchange-fee-calculator/calculate": {
      "post": {
        "operationId": "calculateTravelExchangeFeeCalculator",
        "summary": "Compare what cards, ATMs, DCC and exchange bureaus really cost abroad vs the ECB reference rate.",
        "description": "Enter what you spend abroad and the terms of your payment options – foreign transaction fee, exchange-rate mark-up, ATM fees, “pay in your currency” (dynamic currency conversion) and exchange-bureau spreads. The calculator converts at the ECB reference rate (or your own rate) and shows the effective rate, total cost and extra cost of each option, cheapest first.\n\nMethodology:\n- Reference cost = amount ÷ reference rate (the ECB euro reference rate, cross-calculated via EUR for non-euro pairs, or your manual rate).\n- Each option: cost at its own rate = reference cost × (1 + mark-up %); plus the percentage fee on that amount; plus fixed fees (ATM fixed fees × number of withdrawals). Effective rate = amount ÷ total cost; extra cost = total − reference cost.\n- Dynamic currency conversion (DCC, “pay in your currency”) means the shop’s or ATM operator’s provider converts instead of your card issuer; its mark-up is entered as a rate mark-up. Under Regulation (EU) 2019/518 DCC providers and card issuers in the EU must express their conversion charges as a mark-up over the latest ECB reference rate before you pay, which is the comparison this tool makes.\n- The default rows are illustrative example values, not market data. Replace them with your card’s published terms and the mark-up shown on the terminal or at the exchange counter.\n\nAn estimate: the rate your card network applies on the booking day and any fees not entered here change the final amount.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "travel_exchange_fee_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TravelExchangeFeeCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "500 USD with the example options (manual rate 1 EUR = 1.10 USD)",
                  "value": {
                    "amount": 500,
                    "spendCurrency": "USD",
                    "homeCurrency": "EUR",
                    "rateSource": "manual",
                    "manualRate": 1.1
                  }
                },
                "example2": {
                  "summary": "20,000 JPY, 2 ATM withdrawals (manual rate 1 GBP = 190 JPY)",
                  "value": {
                    "amount": 20000,
                    "spendCurrency": "JPY",
                    "homeCurrency": "GBP",
                    "rateSource": "manual",
                    "manualRate": 190,
                    "atmWithdrawals": 2,
                    "options": [
                      {
                        "name": "Travel card",
                        "method": "card",
                        "feePercent": 0,
                        "markupPercent": 0.4,
                        "fixedFee": 0
                      },
                      {
                        "name": "High-street bank ATM",
                        "method": "atm",
                        "feePercent": 2.99,
                        "markupPercent": 0,
                        "fixedFee": 1.5
                      },
                      {
                        "name": "DCC offer at the ATM",
                        "method": "dcc",
                        "feePercent": 0,
                        "markupPercent": 8,
                        "fixedFee": 1.5
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TravelExchangeFeeCalculatorResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/warranty-value-calculator/calculate": {
      "post": {
        "operationId": "calculateWarrantyValueCalculator",
        "summary": "Is the extended warranty worth it? Expected payout vs. price and break-even failure rate.",
        "description": "Enter the warranty price, how many years it covers, what is already covered by the legal or manufacturer warranty, a failure probability, repair cost, deductible and the chance a claim is approved. The calculator shows the expected payout, the expected net value and the failure rate at which the warranty would break even.\n\nMethodology:\n- Effective years = coverage years − years already covered by the legal/manufacturer warranty (floored at 0); during the overlap the extended warranty adds nothing for defects.\n- Payout per claim = min(average repair cost, item price) − deductible, floored at 0 (a repair costing more than the item is replaced instead).\n- Expected approved claims = effective years × annual failure probability × approval probability (at most one failure per year, years independent).\n- Expected payout = expected approved claims × payout per claim; expected net value = expected payout − warranty price.\n- Probability of at least one approved claim = 1 − (1 − p × a)^years, with p = annual failure probability and a = approval probability.\n- Break-even failure probability = warranty price ÷ (effective years × approval probability × payout per claim).\n- Verdict: worth it if net value > +10% of the warranty price, not worth it if < −10%, otherwise marginal.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "warranty_value_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WarrantyValueCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "TV: 3-year plan over a 2-year legal warranty",
                  "value": {
                    "currency": "EUR",
                    "itemPrice": 900,
                    "warrantyPrice": 99,
                    "coverageYears": 3,
                    "overlapYears": 2,
                    "annualFailureProbabilityPercent": 5,
                    "averageRepairCost": 300,
                    "deductible": 0,
                    "claimApprovalPercent": 80
                  }
                },
                "example2": {
                  "summary": "Phone: 2-year protection with deductible",
                  "value": {
                    "currency": "USD",
                    "itemPrice": 1000,
                    "warrantyPrice": 199,
                    "coverageYears": 2,
                    "overlapYears": 1,
                    "annualFailureProbabilityPercent": 15,
                    "averageRepairCost": 400,
                    "deductible": 99,
                    "claimApprovalPercent": 90
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WarrantyValueCalculatorResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/workflows": {
      "get": {
        "operationId": "listWorkflows",
        "summary": "Multi-tool workflows",
        "responses": {
          "200": {
            "description": "Workflow list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "BillDetectiveInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "unit": {
            "title": "Consumption unit",
            "type": "string",
            "enum": [
              "kWh",
              "m3",
              "liters",
              "GB"
            ],
            "default": "kWh"
          },
          "currency": {
            "title": "Currency",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "changeThresholdPercent": {
            "title": "Flag per-day change vs. previous period above",
            "type": "number",
            "minimum": 1,
            "maximum": 1000,
            "default": 25,
            "x-unit": "%"
          },
          "outlierPercent": {
            "title": "Flag periods above median by more than",
            "type": "number",
            "minimum": 1,
            "maximum": 1000,
            "default": 30,
            "x-unit": "%"
          },
          "tolerancePercent": {
            "title": "Arithmetic tolerance",
            "description": "Billed total may differ from consumption × unit price + charges by this much (rounding, taxes included in prices) before it is flagged.",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 1,
            "x-unit": "%"
          },
          "periods": {
            "title": "Billing periods",
            "description": "Start and end dates are both counted as billing days. Fixed charge = total standing/base charge for the period. Leave Total billed empty to skip the arithmetic check for a row.",
            "type": "array",
            "minItems": 2,
            "maxItems": 36,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "label": {
                  "title": "Period",
                  "type": "string"
                },
                "startDate": {
                  "title": "Start date",
                  "type": "string",
                  "format": "date"
                },
                "endDate": {
                  "title": "End date",
                  "type": "string",
                  "format": "date"
                },
                "consumption": {
                  "title": "Consumption",
                  "type": "number",
                  "minimum": 0
                },
                "unitPrice": {
                  "title": "Unit price",
                  "type": "number",
                  "minimum": 0
                },
                "fixedCharge": {
                  "title": "Fixed / standing charge",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "otherCharges": {
                  "title": "Other charges",
                  "type": "number",
                  "default": 0
                },
                "totalBilled": {
                  "title": "Total billed",
                  "type": "number",
                  "minimum": 0
                }
              },
              "required": [
                "label",
                "startDate",
                "endDate",
                "consumption",
                "unitPrice"
              ]
            }
          }
        },
        "required": [
          "periods"
        ]
      },
      "BillDetectiveResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "unit",
              "currency",
              "averagePerDay",
              "medianPerDay",
              "periods",
              "anomalies",
              "anomalyCount"
            ],
            "properties": {
              "unit": {
                "type": "string"
              },
              "currency": {
                "type": "string"
              },
              "averagePerDay": {
                "type": "number"
              },
              "medianPerDay": {
                "type": "number"
              },
              "iqr": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "q1",
                  "q3",
                  "lowerFence",
                  "upperFence"
                ],
                "properties": {
                  "q1": {
                    "type": "number"
                  },
                  "q3": {
                    "type": "number"
                  },
                  "lowerFence": {
                    "type": "number"
                  },
                  "upperFence": {
                    "type": "number"
                  }
                }
              },
              "anomalyCount": {
                "type": "integer"
              },
              "periods": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "label",
                    "startDate",
                    "endDate",
                    "days",
                    "consumption",
                    "perDay",
                    "changeVsPreviousPercent",
                    "vsAveragePercent",
                    "unitPrice",
                    "fixedCharge",
                    "fixedChargePerDay",
                    "computedTotal",
                    "totalBilled",
                    "difference",
                    "costPerDay"
                  ],
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "startDate": {
                      "type": "string",
                      "format": "date"
                    },
                    "endDate": {
                      "type": "string",
                      "format": "date"
                    },
                    "days": {
                      "type": "integer"
                    },
                    "consumption": {
                      "type": "number"
                    },
                    "perDay": {
                      "type": "number"
                    },
                    "changeVsPreviousPercent": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "vsAveragePercent": {
                      "type": "number"
                    },
                    "unitPrice": {
                      "type": "number"
                    },
                    "fixedCharge": {
                      "type": "number"
                    },
                    "fixedChargePerDay": {
                      "type": "number"
                    },
                    "computedTotal": {
                      "type": "number"
                    },
                    "totalBilled": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "difference": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Total billed − computed total."
                    },
                    "costPerDay": {
                      "type": "number",
                      "description": "Billed total (or computed total if not given) ÷ days."
                    }
                  }
                }
              },
              "anomalies": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "period",
                    "type",
                    "severity",
                    "explanation"
                  ],
                  "properties": {
                    "period": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "consumption-change",
                        "above-average",
                        "outlier-iqr",
                        "outlier-median",
                        "unit-price-change",
                        "fixed-charge-change",
                        "arithmetic-mismatch",
                        "period-overlap",
                        "period-gap"
                      ]
                    },
                    "severity": {
                      "type": "string",
                      "enum": [
                        "info",
                        "warning",
                        "alert"
                      ]
                    },
                    "explanation": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "BreakEvenExplorerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "preset": {
            "title": "Preset",
            "description": "Presets only fill in escalation, degradation and horizon when you leave those empty; the values are listed under assumptions.",
            "type": "string",
            "enum": [
              "custom",
              "solar",
              "ev",
              "heat-pump"
            ],
            "default": "custom"
          },
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "upfrontInvestment": {
            "title": "Upfront investment",
            "type": "number",
            "minimum": 0,
            "default": 10000
          },
          "subsidies": {
            "title": "Subsidies / grants / tax credits",
            "description": "Received at the start; reduces the net investment.",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "annualSavings": {
            "title": "Savings in year 1",
            "type": "number",
            "default": 1200,
            "x-unit": "per year"
          },
          "savingsEscalationPercent": {
            "title": "Savings escalation (e.g. energy price inflation)",
            "description": "Empty = preset default.",
            "type": "number",
            "minimum": -50,
            "maximum": 50,
            "x-unit": "%/year"
          },
          "degradationPercent": {
            "title": "Performance degradation",
            "description": "Empty = preset default.",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "x-unit": "%/year"
          },
          "maintenancePerYear": {
            "title": "Maintenance per year",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "discountRatePercent": {
            "title": "Discount rate",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 3,
            "x-unit": "%/year"
          },
          "horizonYears": {
            "title": "Horizon",
            "description": "Empty = preset default.",
            "type": "integer",
            "minimum": 1,
            "maximum": 60,
            "x-unit": "years"
          },
          "savingsByYear": {
            "title": "Override savings for specific years (optional)",
            "description": "Savings entered here replace the escalated/degraded value for that year (before maintenance).",
            "type": "array",
            "maxItems": 60,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "year": {
                  "title": "Year",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                },
                "savings": {
                  "title": "Savings",
                  "type": "number"
                }
              },
              "required": [
                "year",
                "savings"
              ]
            }
          }
        },
        "required": [
          "upfrontInvestment",
          "annualSavings"
        ]
      },
      "BreakEvenExplorerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "preset",
              "currency",
              "netInvestment",
              "horizonYears",
              "escalationPercent",
              "degradationPercent",
              "discountRatePercent",
              "simplePaybackYears",
              "discountedPaybackYears",
              "npv",
              "irrPercent",
              "totalNetSavings",
              "roiPercent",
              "years"
            ],
            "properties": {
              "preset": {
                "type": "string",
                "enum": [
                  "custom",
                  "solar",
                  "ev",
                  "heat-pump"
                ]
              },
              "currency": {
                "type": "string"
              },
              "netInvestment": {
                "type": "number",
                "description": "Upfront investment − subsidies."
              },
              "horizonYears": {
                "type": "integer"
              },
              "escalationPercent": {
                "type": "number"
              },
              "degradationPercent": {
                "type": "number"
              },
              "discountRatePercent": {
                "type": "number"
              },
              "simplePaybackYears": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Fractional years until cumulative nominal cash flow reaches 0; null if not within the horizon."
              },
              "discountedPaybackYears": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "npv": {
                "type": "number"
              },
              "irrPercent": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Internal rate of return in %; null if none exists in [−99%, 1000%]."
              },
              "totalNetSavings": {
                "type": "number",
                "description": "Sum of yearly net cash flows (savings − maintenance) over the horizon."
              },
              "roiPercent": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "(total net savings − net investment) ÷ net investment; null when net investment is 0."
              },
              "years": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "year",
                    "savings",
                    "maintenance",
                    "netCashFlow",
                    "cumulative",
                    "discountedCashFlow",
                    "discountedCumulative"
                  ],
                  "properties": {
                    "year": {
                      "type": "integer"
                    },
                    "savings": {
                      "type": "number"
                    },
                    "maintenance": {
                      "type": "number"
                    },
                    "netCashFlow": {
                      "type": "number"
                    },
                    "cumulative": {
                      "type": "number"
                    },
                    "discountedCashFlow": {
                      "type": "number"
                    },
                    "discountedCumulative": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "BulkBuyBrainInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "unitLabel": {
            "title": "Unit",
            "type": "string",
            "maxLength": 20,
            "default": "units"
          },
          "smallPrice": {
            "title": "Small pack price",
            "type": "number",
            "minimum": 0.000001,
            "default": 3
          },
          "smallQuantity": {
            "title": "Small pack quantity",
            "type": "number",
            "minimum": 0.000001,
            "default": 1
          },
          "bulkPrice": {
            "title": "Bulk pack price",
            "type": "number",
            "minimum": 0.000001,
            "default": 20
          },
          "bulkQuantity": {
            "title": "Bulk pack quantity",
            "type": "number",
            "minimum": 0.000001,
            "default": 10
          },
          "consumptionPerWeek": {
            "title": "You use per week",
            "type": "number",
            "minimum": 0.000001,
            "default": 1
          },
          "shelfLifeWeeks": {
            "title": "Shelf life once bought",
            "description": "Leave empty for non-perishables.",
            "type": "number",
            "minimum": 0.1,
            "x-unit": "weeks"
          },
          "storageCostPerWeek": {
            "title": "Storage cost of the bulk pack",
            "description": "Optional: freezer energy, rented space…",
            "type": "number",
            "minimum": 0,
            "default": 0,
            "x-unit": "per week"
          },
          "interestRatePercent": {
            "title": "Interest your cash could earn",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 3,
            "x-unit": "%/year"
          }
        },
        "required": [
          "smallPrice",
          "smallQuantity",
          "bulkPrice",
          "bulkQuantity",
          "consumptionPerWeek"
        ]
      },
      "BulkBuyBrainResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "currency",
              "unit",
              "verdict",
              "smallPricePerUnit",
              "bulkPricePerUnit",
              "effectiveBulkCostPerUsedUnit",
              "usedQuantity",
              "wastedQuantity",
              "wastePercent",
              "weeksToUse",
              "storageCost",
              "opportunityCost",
              "savings",
              "savingsPercent",
              "maxWorthwhileQuantity",
              "maxWorthwhileWeeksOfSupply"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "unit": {
                "type": "string"
              },
              "verdict": {
                "type": "string",
                "enum": [
                  "buy-bulk",
                  "buy-small",
                  "no-difference"
                ]
              },
              "smallPricePerUnit": {
                "type": "number"
              },
              "bulkPricePerUnit": {
                "type": "number",
                "description": "Sticker unit price of the bulk pack."
              },
              "effectiveBulkCostPerUsedUnit": {
                "type": "number",
                "description": "(bulk price + storage + opportunity cost) ÷ units actually used."
              },
              "usedQuantity": {
                "type": "number"
              },
              "wastedQuantity": {
                "type": "number"
              },
              "wastePercent": {
                "type": "number"
              },
              "weeksToUse": {
                "type": "number",
                "description": "Weeks the bulk pack is held (until used up or expired)."
              },
              "storageCost": {
                "type": "number"
              },
              "opportunityCost": {
                "type": "number"
              },
              "savings": {
                "type": "number",
                "description": "Small-pack cost of the used units − total bulk cost; negative = loss."
              },
              "savingsPercent": {
                "type": "number"
              },
              "maxWorthwhileQuantity": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Largest pack size (at the bulk unit price) that is still no more expensive than small packs; null = no limit."
              },
              "maxWorthwhileWeeksOfSupply": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "CurrencyConverterInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "amount": {
            "title": "Amount",
            "type": "number",
            "minimum": 0,
            "maximum": 1000000000000000,
            "default": 100
          },
          "from": {
            "title": "From currency",
            "description": "ISO code, e.g. EUR, USD, GBP, CHF, JPY.",
            "type": "string",
            "maxLength": 3,
            "default": "EUR"
          },
          "to": {
            "title": "To currencies",
            "description": "One or more ISO codes, comma-separated, or \"all\".",
            "type": "string",
            "maxLength": 200,
            "default": "USD, GBP, CHF, JPY"
          },
          "date": {
            "title": "Rate date",
            "description": "Optional: use the reference rates of this date (the last publication on or before it). Empty: latest rates.",
            "type": "string",
            "format": "date"
          }
        },
        "required": [
          "amount",
          "from",
          "to"
        ]
      },
      "CurrencyConverterResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "amount",
              "from",
              "rates",
              "conversions"
            ],
            "properties": {
              "amount": {
                "type": "number"
              },
              "from": {
                "type": "string"
              },
              "rates": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "date",
                  "source",
                  "url",
                  "fetchedAt",
                  "stale"
                ],
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Reference date of the rates."
                  },
                  "requestedDate": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "ecb-daily",
                      "ecb-sdmx",
                      "manual"
                    ]
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "fetchedAt": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "stale": {
                    "type": "boolean"
                  }
                }
              },
              "conversions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "currency",
                    "name",
                    "rate",
                    "inverseRate",
                    "amount",
                    "rounded"
                  ],
                  "properties": {
                    "currency": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "rate": {
                      "type": "number",
                      "description": "Units of the target currency per 1 unit of the source currency."
                    },
                    "inverseRate": {
                      "type": "number"
                    },
                    "amount": {
                      "type": "number"
                    },
                    "rounded": {
                      "type": "number",
                      "description": "Rounded to the currency’s minor units."
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "HiddenFeeFinderInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "text": {
            "title": "Quote / contract / invoice lines",
            "description": "One cost per line. Supported amount formats: 1,234.56 / 1.234,56 / €12 / 12 EUR / 12,50 € and percentages like 19% or 2.5 %.",
            "type": "string",
            "maxLength": 50000
          },
          "items": {
            "title": "Or: line items",
            "description": "Optional alternative to pasting text. Rows are appended after the pasted lines.",
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "description": {
                  "title": "Description",
                  "type": "string"
                },
                "amount": {
                  "title": "Amount",
                  "type": "number"
                }
              },
              "required": [
                "description",
                "amount"
              ]
            }
          },
          "currency": {
            "title": "Currency",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "numberFormat": {
            "title": "Number format",
            "type": "string",
            "enum": [
              "auto",
              "en",
              "de"
            ],
            "default": "auto"
          }
        },
        "required": []
      },
      "HiddenFeeFinderResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "currency",
              "lines",
              "totals",
              "statedTotal",
              "statedTotalDifference",
              "hiddenOrOptional",
              "conditionalCharges"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "statedTotal": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Amount on the last line recognised as a total, if any."
              },
              "statedTotalDifference": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Stated total − computed payable total."
              },
              "totals": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "base",
                  "fees",
                  "taxes",
                  "surcharges",
                  "insuranceAddons",
                  "recurring",
                  "discounts",
                  "payableTotal",
                  "nonBaseCosts",
                  "nonBaseSharePercent",
                  "deposits",
                  "conditionalPotential",
                  "upfrontCashInclDeposits"
                ],
                "properties": {
                  "base": {
                    "type": "number"
                  },
                  "fees": {
                    "type": "number"
                  },
                  "taxes": {
                    "type": "number"
                  },
                  "surcharges": {
                    "type": "number"
                  },
                  "insuranceAddons": {
                    "type": "number"
                  },
                  "recurring": {
                    "type": "number"
                  },
                  "discounts": {
                    "type": "number",
                    "description": "Negative number."
                  },
                  "payableTotal": {
                    "type": "number",
                    "description": "Sum of all lines included in the total (excludes deposits, conditional charges, totals and taxes stated as included)."
                  },
                  "nonBaseCosts": {
                    "type": "number",
                    "description": "fees + taxes + surcharges + insurance/add-ons + recurring (non-base lines)."
                  },
                  "nonBaseSharePercent": {
                    "type": "number",
                    "description": "nonBaseCosts ÷ payableTotal × 100."
                  },
                  "deposits": {
                    "type": "number"
                  },
                  "conditionalPotential": {
                    "type": "number",
                    "description": "Sum of conditional charges (only due if triggered)."
                  },
                  "upfrontCashInclDeposits": {
                    "type": "number"
                  }
                }
              },
              "lines": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "lineNumber",
                    "text",
                    "amount",
                    "percent",
                    "amountSource",
                    "category",
                    "matchedKeyword",
                    "recurring",
                    "recurringKeyword",
                    "conditional",
                    "conditionalKeyword",
                    "optional",
                    "optionalKeyword",
                    "includedTax",
                    "includedInTotal"
                  ],
                  "properties": {
                    "lineNumber": {
                      "type": "integer"
                    },
                    "text": {
                      "type": "string"
                    },
                    "amount": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "percent": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "amountSource": {
                      "type": "string",
                      "enum": [
                        "stated",
                        "derived-from-percent",
                        "none"
                      ]
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "base",
                        "fee",
                        "tax",
                        "surcharge",
                        "penalty",
                        "deposit",
                        "discount",
                        "insurance-addon",
                        "recurring",
                        "total",
                        "info"
                      ]
                    },
                    "matchedKeyword": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Keyword that triggered the category (null = default base price)."
                    },
                    "recurring": {
                      "type": "boolean"
                    },
                    "recurringKeyword": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "conditional": {
                      "type": "boolean"
                    },
                    "conditionalKeyword": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "optional": {
                      "type": "boolean"
                    },
                    "optionalKeyword": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "includedTax": {
                      "type": "boolean",
                      "description": "Tax stated as already included (e.g. \"incl. VAT\"); not added again."
                    },
                    "includedInTotal": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "hiddenOrOptional": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "conditionalCharges": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "HistoricalFxChartInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "base": {
            "title": "Base currency",
            "description": "The rate shows how many units of the quote currency one unit of the base buys.",
            "type": "string",
            "maxLength": 3,
            "default": "EUR"
          },
          "quotes": {
            "title": "Quote currencies",
            "description": "Up to 6 ISO codes, comma-separated.",
            "type": "string",
            "maxLength": 100,
            "default": "USD, GBP"
          },
          "start": {
            "title": "From",
            "description": "Default: one year before the end date. Earliest 1999-01-04.",
            "type": "string",
            "format": "date"
          },
          "end": {
            "title": "To",
            "description": "Default: today.",
            "type": "string",
            "format": "date"
          },
          "normalise": {
            "title": "Index to 100 at the start",
            "type": "string",
            "enum": [
              "auto",
              "yes",
              "no"
            ],
            "default": "auto"
          },
          "png": {
            "title": "Also create a PNG image",
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "base",
          "quotes"
        ]
      },
      "HistoricalFxChartResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "base",
              "quotes",
              "start",
              "end",
              "normalised",
              "pairs",
              "svg",
              "png",
              "dataUrl"
            ],
            "properties": {
              "base": {
                "type": "string"
              },
              "quotes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "start": {
                "type": "string"
              },
              "end": {
                "type": "string"
              },
              "normalised": {
                "type": "boolean"
              },
              "pairs": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "pair",
                    "base",
                    "quote",
                    "observations",
                    "start",
                    "end",
                    "min",
                    "max",
                    "average",
                    "changePercent",
                    "annualisedVolatilityPercent"
                  ],
                  "properties": {
                    "pair": {
                      "type": "string"
                    },
                    "base": {
                      "type": "string"
                    },
                    "quote": {
                      "type": "string"
                    },
                    "observations": {
                      "type": "integer"
                    },
                    "start": {
                      "type": "object"
                    },
                    "end": {
                      "type": "object"
                    },
                    "min": {
                      "type": "object"
                    },
                    "max": {
                      "type": "object"
                    },
                    "average": {
                      "type": "number"
                    },
                    "changePercent": {
                      "type": "number"
                    },
                    "annualisedVolatilityPercent": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  }
                }
              },
              "svg": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              },
              "png": {
                "anyOf": [
                  {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "artifactId",
                      "filename",
                      "mime",
                      "bytes",
                      "sha256",
                      "path"
                    ],
                    "properties": {
                      "artifactId": {
                        "type": "string"
                      },
                      "filename": {
                        "type": "string"
                      },
                      "mime": {
                        "type": "string"
                      },
                      "bytes": {
                        "type": "integer"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string",
                        "description": "Download path on the same site (valid for about 2 hours)."
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "dataUrl": {
                "type": "string",
                "description": "ECB Data Portal request used."
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "InflationTimeMachineInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "amount": {
            "title": "Amount",
            "type": "number",
            "minimum": 0,
            "default": 100
          },
          "currency": {
            "title": "Currency label",
            "description": "Label only. Use the currency of the chosen country; no conversion is done.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "fromYear": {
            "title": "From year",
            "type": "integer",
            "minimum": 1960,
            "maximum": 2100,
            "default": 2000
          },
          "toYear": {
            "title": "To year",
            "type": "integer",
            "minimum": 1960,
            "maximum": 2100,
            "default": 2024
          },
          "country": {
            "title": "Country",
            "type": "string",
            "enum": [
              "US",
              "GB",
              "DE",
              "FR",
              "IT",
              "ES",
              "NL",
              "AT",
              "CH",
              "SE",
              "PL",
              "CA",
              "AU",
              "JP",
              "IN",
              "BR",
              "MX",
              "ZA",
              "TR",
              "CN"
            ],
            "default": "DE"
          }
        },
        "required": [
          "amount",
          "fromYear",
          "toYear"
        ]
      },
      "InflationTimeMachineResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "country",
              "countryName",
              "amount",
              "fromYear",
              "toYear",
              "cpiFrom",
              "cpiTo",
              "equivalentValue",
              "cumulativeInflationPercent",
              "averageAnnualInflationPercent",
              "purchasingPowerLossPercent",
              "years",
              "dataLastUpdated"
            ],
            "properties": {
              "country": {
                "type": "string"
              },
              "countryName": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              },
              "fromYear": {
                "type": "integer"
              },
              "toYear": {
                "type": "integer"
              },
              "cpiFrom": {
                "type": "number",
                "description": "CPI (2010 = 100) in the from-year."
              },
              "cpiTo": {
                "type": "number",
                "description": "CPI (2010 = 100) in the to-year."
              },
              "equivalentValue": {
                "type": "number",
                "description": "amount × CPI(to) ÷ CPI(from)."
              },
              "cumulativeInflationPercent": {
                "type": "number",
                "description": "(CPI(to) ÷ CPI(from) − 1) × 100."
              },
              "averageAnnualInflationPercent": {
                "type": "number",
                "description": "Compound annual rate between the earlier and later year."
              },
              "purchasingPowerLossPercent": {
                "type": "number",
                "description": "Share of purchasing power lost from the earlier to the later year (negative = gain)."
              },
              "dataLastUpdated": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "years": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "year",
                    "cpi",
                    "value",
                    "inflationPercent"
                  ],
                  "properties": {
                    "year": {
                      "type": "integer"
                    },
                    "cpi": {
                      "type": "number"
                    },
                    "value": {
                      "type": "number",
                      "description": "Amount expressed in this year's prices."
                    },
                    "inflationPercent": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Change of CPI vs. the previous listed year."
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "MultiCurrencyInvoiceCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "invoiceCurrency": {
            "title": "Invoice currency",
            "type": "string",
            "maxLength": 3,
            "default": "EUR"
          },
          "rateSource": {
            "title": "Exchange rates",
            "type": "string",
            "enum": [
              "ecb",
              "manual"
            ],
            "default": "ecb"
          },
          "rateDate": {
            "title": "Rate date",
            "description": "E.g. the invoice or supply date. Empty: latest ECB rates. Weekends/holidays use the last publication before.",
            "type": "string",
            "format": "date"
          },
          "manualRates": {
            "title": "Manual rates",
            "description": "One per line, as units per 1 EUR (ECB convention). Used with \"Enter rates manually\".",
            "type": "string",
            "maxLength": 4000
          },
          "lines": {
            "title": "Invoice lines",
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "description": {
                  "title": "Description",
                  "type": "string",
                  "maxLength": 200
                },
                "quantity": {
                  "title": "Quantity",
                  "type": "number",
                  "minimum": -1000000000,
                  "maximum": 1000000000,
                  "default": 1
                },
                "unitPrice": {
                  "title": "Unit price",
                  "type": "number",
                  "minimum": -1000000000000,
                  "maximum": 1000000000000
                },
                "currency": {
                  "title": "Currency",
                  "type": "string",
                  "maxLength": 3
                },
                "vatRate": {
                  "title": "VAT %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "default": 0
                }
              },
              "required": [
                "unitPrice"
              ]
            }
          },
          "rounding": {
            "title": "Rounding",
            "type": "string",
            "enum": [
              "line",
              "document"
            ],
            "default": "line"
          }
        },
        "required": [
          "invoiceCurrency",
          "lines"
        ]
      },
      "MultiCurrencyInvoiceCalculatorResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "invoiceCurrency",
              "rounding",
              "rates",
              "ratesUsed",
              "lines",
              "vatSummary",
              "totals",
              "alternativeRounding",
              "roundingDifference",
              "statement"
            ],
            "properties": {
              "invoiceCurrency": {
                "type": "string"
              },
              "rounding": {
                "type": "string"
              },
              "rates": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "date",
                  "source",
                  "url",
                  "fetchedAt",
                  "stale"
                ],
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Reference date of the rates."
                  },
                  "requestedDate": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "ecb-daily",
                      "ecb-sdmx",
                      "manual"
                    ]
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "fetchedAt": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "stale": {
                    "type": "boolean"
                  }
                }
              },
              "ratesUsed": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "currency",
                    "perEur",
                    "toInvoice"
                  ],
                  "properties": {
                    "currency": {
                      "type": "string"
                    },
                    "perEur": {
                      "type": "number"
                    },
                    "toInvoice": {
                      "type": "number",
                      "description": "Invoice-currency units per 1 unit of this currency."
                    }
                  }
                }
              },
              "lines": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "description",
                    "quantity",
                    "unitPrice",
                    "currency",
                    "netOriginal",
                    "rate",
                    "net",
                    "vatRate",
                    "vat",
                    "gross"
                  ],
                  "properties": {
                    "description": {
                      "type": "string"
                    },
                    "quantity": {
                      "type": "number"
                    },
                    "unitPrice": {
                      "type": "number"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "netOriginal": {
                      "type": "number"
                    },
                    "rate": {
                      "type": "number"
                    },
                    "net": {
                      "type": "number"
                    },
                    "vatRate": {
                      "type": "number"
                    },
                    "vat": {
                      "type": "number"
                    },
                    "gross": {
                      "type": "number"
                    }
                  }
                }
              },
              "vatSummary": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "vatRate",
                    "net",
                    "vat",
                    "gross"
                  ],
                  "properties": {
                    "vatRate": {
                      "type": "number"
                    },
                    "net": {
                      "type": "number"
                    },
                    "vat": {
                      "type": "number"
                    },
                    "gross": {
                      "type": "number"
                    }
                  }
                }
              },
              "totals": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "net",
                  "vat",
                  "gross"
                ],
                "properties": {
                  "net": {
                    "type": "number"
                  },
                  "vat": {
                    "type": "number"
                  },
                  "gross": {
                    "type": "number"
                  }
                }
              },
              "alternativeRounding": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "method",
                  "net",
                  "vat",
                  "gross"
                ],
                "properties": {
                  "method": {
                    "type": "string"
                  },
                  "net": {
                    "type": "number"
                  },
                  "vat": {
                    "type": "number"
                  },
                  "gross": {
                    "type": "number"
                  }
                }
              },
              "roundingDifference": {
                "type": "number",
                "description": "Gross total of this method minus the other method."
              },
              "statement": {
                "type": "string"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "PricePerEverythingInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "basis": {
            "title": "Compare by",
            "type": "string",
            "enum": [
              "unit",
              "use",
              "serving",
              "hour",
              "year"
            ],
            "default": "unit"
          },
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "products": {
            "title": "Products",
            "description": "Quantity is in the chosen basis (e.g. 500 for 500 g, 30 for 30 uses). Waste % reduces the usable quantity.",
            "type": "array",
            "minItems": 2,
            "maxItems": 20,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Product",
                  "type": "string"
                },
                "price": {
                  "title": "Price",
                  "type": "number",
                  "minimum": 0
                },
                "quantity": {
                  "title": "Quantity",
                  "type": "number",
                  "minimum": 0.000001
                },
                "unit": {
                  "title": "Unit label",
                  "type": "string"
                },
                "wastePercent": {
                  "title": "Waste %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 99,
                  "default": 0
                }
              },
              "required": [
                "name",
                "price",
                "quantity"
              ]
            }
          }
        },
        "required": [
          "products"
        ]
      },
      "PricePerEverythingResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "basis",
              "currency",
              "ranking",
              "cheapest",
              "savingsVsMostExpensivePercent"
            ],
            "properties": {
              "basis": {
                "type": "string"
              },
              "currency": {
                "type": "string"
              },
              "cheapest": {
                "type": "string"
              },
              "savingsVsMostExpensivePercent": {
                "type": "number"
              },
              "ranking": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "rank",
                    "name",
                    "price",
                    "usableQuantity",
                    "pricePerUnit",
                    "unit",
                    "relativeToCheapest"
                  ],
                  "properties": {
                    "rank": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "price": {
                      "type": "number"
                    },
                    "usableQuantity": {
                      "type": "number"
                    },
                    "pricePerUnit": {
                      "type": "number"
                    },
                    "unit": {
                      "type": "string"
                    },
                    "relativeToCheapest": {
                      "type": "number",
                      "description": "Price per unit divided by the cheapest price per unit."
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "SalaryRealityCheckInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "offers": {
            "title": "Job offers",
            "description": "Tax rate is YOUR effective tax + social contribution rate in % of gross (look it up in a local net-pay calculator); this tool does not implement any national tax law. Living cost difference is extra rent/living cost per month for this job compared to your baseline (negative if cheaper).",
            "type": "array",
            "minItems": 2,
            "maxItems": 4,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Offer",
                  "type": "string"
                },
                "grossAnnual": {
                  "title": "Gross salary / year",
                  "type": "number",
                  "minimum": 0
                },
                "taxRatePercent": {
                  "title": "Effective tax + social %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 95,
                  "x-unit": "%"
                },
                "bonus": {
                  "title": "Bonus / year",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "benefitsValue": {
                  "title": "Benefits value / year",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "commuteMinutesOneWay": {
                  "title": "Commute one way",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 600,
                  "default": 0,
                  "x-unit": "min"
                },
                "commuteCostMonthly": {
                  "title": "Commute cost / month",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "workingDays": {
                  "title": "Working days / year",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 366,
                  "default": 220,
                  "x-unit": "days"
                },
                "weeklyHours": {
                  "title": "Hours / week",
                  "type": "number",
                  "minimum": 1,
                  "maximum": 100,
                  "default": 40,
                  "x-unit": "h"
                },
                "livingCostDiffMonthly": {
                  "title": "Living cost difference / month",
                  "type": "number",
                  "default": 0
                },
                "remoteDaysPerWeek": {
                  "title": "Remote days / week",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 5,
                  "default": 0,
                  "x-unit": "days"
                }
              },
              "required": [
                "name",
                "grossAnnual",
                "taxRatePercent"
              ]
            }
          }
        },
        "required": [
          "offers"
        ]
      },
      "SalaryRealityCheckResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "currency",
              "best",
              "ranking"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "best": {
                "type": "string",
                "description": "Offer with the highest net income per hour of working plus commuting, after commute and living costs."
              },
              "ranking": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "rank",
                    "name",
                    "grossTotal",
                    "netAnnual",
                    "commuteDaysPerYear",
                    "commuteHoursPerYear",
                    "commuteCostPerYear",
                    "livingCostDiffPerYear",
                    "netAfterCosts",
                    "workHoursPerYear",
                    "netPerWorkHour",
                    "netPerHourInclCommute",
                    "commuteTimeValue",
                    "differenceToBestPerYear"
                  ],
                  "properties": {
                    "rank": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "grossTotal": {
                      "type": "number",
                      "description": "Gross salary + bonus."
                    },
                    "netAnnual": {
                      "type": "number",
                      "description": "(Gross + bonus) × (1 − tax rate) + benefits."
                    },
                    "commuteDaysPerYear": {
                      "type": "number"
                    },
                    "commuteHoursPerYear": {
                      "type": "number"
                    },
                    "commuteCostPerYear": {
                      "type": "number"
                    },
                    "livingCostDiffPerYear": {
                      "type": "number"
                    },
                    "netAfterCosts": {
                      "type": "number",
                      "description": "Net annual − commute cost − living cost difference."
                    },
                    "workHoursPerYear": {
                      "type": "number"
                    },
                    "netPerWorkHour": {
                      "type": "number",
                      "description": "Net annual ÷ work hours (ignores commute)."
                    },
                    "netPerHourInclCommute": {
                      "type": "number",
                      "description": "Net after costs ÷ (work hours + commute hours)."
                    },
                    "commuteTimeValue": {
                      "type": "number",
                      "description": "Commute hours valued at the net hourly wage of this offer."
                    },
                    "differenceToBestPerYear": {
                      "type": "number",
                      "description": "Net after costs of the best offer minus this offer."
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "SubscriptionStackInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "minUsesPerMonth": {
            "title": "Flag if used fewer than",
            "type": "number",
            "minimum": 0,
            "maximum": 1000,
            "default": 2,
            "x-unit": "times/month"
          },
          "maxCostPerUse": {
            "title": "Flag if cost per use is above",
            "description": "In your currency, for your share.",
            "type": "number",
            "minimum": 0,
            "default": 5
          },
          "subscriptions": {
            "title": "Subscriptions",
            "description": "Price is the full price per billing period; “Shared by” splits it (you pay price ÷ people). Leave uses empty if unknown.",
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Service",
                  "type": "string"
                },
                "price": {
                  "title": "Price",
                  "type": "number",
                  "minimum": 0
                },
                "billing": {
                  "title": "Billed",
                  "type": "string",
                  "enum": [
                    "weekly",
                    "monthly",
                    "quarterly",
                    "yearly"
                  ],
                  "default": "monthly"
                },
                "category": {
                  "title": "Category",
                  "type": "string",
                  "enum": [
                    "video",
                    "music",
                    "news",
                    "software",
                    "cloud-storage",
                    "fitness",
                    "gaming",
                    "food",
                    "other"
                  ],
                  "default": "other"
                },
                "usesPerMonth": {
                  "title": "Uses / month",
                  "type": "number",
                  "minimum": 0
                },
                "sharedBy": {
                  "title": "Shared by",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 20,
                  "default": 1,
                  "x-unit": "people"
                }
              },
              "required": [
                "name",
                "price"
              ]
            }
          }
        },
        "required": [
          "subscriptions"
        ]
      },
      "SubscriptionStackResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "currency",
              "monthlyTotal",
              "annualTotal",
              "fullPriceMonthlyTotal",
              "subscriptions",
              "categories",
              "overlaps",
              "cancelCandidates",
              "savingsIfCancelled"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "monthlyTotal": {
                "type": "number",
                "description": "Your share, per month."
              },
              "annualTotal": {
                "type": "number"
              },
              "fullPriceMonthlyTotal": {
                "type": "number",
                "description": "Before splitting shared subscriptions."
              },
              "subscriptions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "name",
                    "category",
                    "billing",
                    "monthlyFullPrice",
                    "monthlyYourShare",
                    "annualYourShare",
                    "usesPerMonth",
                    "costPerUse",
                    "shareOfTotalPercent",
                    "cancelCandidate",
                    "reasons"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "video",
                        "music",
                        "news",
                        "software",
                        "cloud-storage",
                        "fitness",
                        "gaming",
                        "food",
                        "other"
                      ]
                    },
                    "billing": {
                      "type": "string",
                      "enum": [
                        "weekly",
                        "monthly",
                        "quarterly",
                        "yearly"
                      ]
                    },
                    "monthlyFullPrice": {
                      "type": "number"
                    },
                    "monthlyYourShare": {
                      "type": "number"
                    },
                    "annualYourShare": {
                      "type": "number"
                    },
                    "usesPerMonth": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "costPerUse": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Your monthly share ÷ uses per month; null if uses unknown or 0."
                    },
                    "shareOfTotalPercent": {
                      "type": "number"
                    },
                    "cancelCandidate": {
                      "type": "boolean"
                    },
                    "reasons": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "categories": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "category",
                    "label",
                    "count",
                    "monthly",
                    "annual"
                  ],
                  "properties": {
                    "category": {
                      "type": "string",
                      "enum": [
                        "video",
                        "music",
                        "news",
                        "software",
                        "cloud-storage",
                        "fitness",
                        "gaming",
                        "food",
                        "other"
                      ]
                    },
                    "label": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "monthly": {
                      "type": "number"
                    },
                    "annual": {
                      "type": "number"
                    }
                  }
                }
              },
              "overlaps": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "category",
                    "services",
                    "monthly"
                  ],
                  "properties": {
                    "category": {
                      "type": "string",
                      "enum": [
                        "video",
                        "music",
                        "news",
                        "software",
                        "cloud-storage",
                        "fitness",
                        "gaming",
                        "food",
                        "other"
                      ]
                    },
                    "services": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "monthly": {
                      "type": "number"
                    }
                  }
                }
              },
              "cancelCandidates": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "savingsIfCancelled": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "monthly",
                  "annual"
                ],
                "properties": {
                  "monthly": {
                    "type": "number"
                  },
                  "annual": {
                    "type": "number"
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "TravelExchangeFeeCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "amount": {
            "title": "Amount spent abroad",
            "description": "In the local (spending) currency.",
            "type": "number",
            "minimum": 0.01,
            "maximum": 1000000000,
            "default": 500
          },
          "spendCurrency": {
            "title": "Spending currency",
            "type": "string",
            "maxLength": 3,
            "default": "USD"
          },
          "homeCurrency": {
            "title": "Home (account) currency",
            "type": "string",
            "maxLength": 3,
            "default": "EUR"
          },
          "rateSource": {
            "title": "Reference rate",
            "type": "string",
            "enum": [
              "ecb",
              "manual"
            ],
            "default": "ecb"
          },
          "manualRate": {
            "title": "Manual rate: 1 home-currency unit =",
            "description": "…units of the spending currency (e.g. 1 EUR = 1.10 USD → 1.10). Used with \"Enter a rate manually\", or as fallback when the ECB is unreachable.",
            "type": "number",
            "minimum": 0.000001,
            "maximum": 1000000000
          },
          "options": {
            "title": "Payment options",
            "description": "Example values – replace them with your own card’s and provider’s terms. EU card issuers must state their conversion mark-up over the ECB rate.",
            "type": "array",
            "maxItems": 12,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Option",
                  "type": "string",
                  "maxLength": 80
                },
                "method": {
                  "title": "Type",
                  "type": "string",
                  "enum": [
                    "card",
                    "atm",
                    "dcc",
                    "exchange"
                  ],
                  "default": "card"
                },
                "feePercent": {
                  "title": "Fee %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 50,
                  "x-unit": "%"
                },
                "markupPercent": {
                  "title": "Rate mark-up %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 50,
                  "x-unit": "%"
                },
                "fixedFee": {
                  "title": "Fixed fee (home currency)",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 10000
                }
              },
              "required": []
            },
            "default": [
              {
                "name": "Card without foreign transaction fee",
                "method": "card",
                "feePercent": 0,
                "markupPercent": 0,
                "fixedFee": 0
              },
              {
                "name": "Card with foreign transaction fee",
                "method": "card",
                "feePercent": 1.75,
                "markupPercent": 0,
                "fixedFee": 0
              },
              {
                "name": "ATM cash withdrawal",
                "method": "atm",
                "feePercent": 1,
                "markupPercent": 0,
                "fixedFee": 4
              },
              {
                "name": "\"Pay in your currency\" (DCC) at the terminal",
                "method": "dcc",
                "feePercent": 0,
                "markupPercent": 5,
                "fixedFee": 0
              },
              {
                "name": "Exchange bureau cash",
                "method": "exchange",
                "feePercent": 0,
                "markupPercent": 7,
                "fixedFee": 0
              }
            ]
          },
          "atmWithdrawals": {
            "title": "Number of ATM withdrawals",
            "description": "The fixed fee of ATM options is charged per withdrawal.",
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 1
          }
        },
        "required": [
          "amount",
          "spendCurrency",
          "homeCurrency"
        ]
      },
      "TravelExchangeFeeCalculatorResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "amount",
              "spendCurrency",
              "homeCurrency",
              "referenceRate",
              "referenceCost",
              "rates",
              "options",
              "cheapest",
              "mostExpensive"
            ],
            "properties": {
              "amount": {
                "type": "number"
              },
              "spendCurrency": {
                "type": "string"
              },
              "homeCurrency": {
                "type": "string"
              },
              "referenceRate": {
                "type": "number",
                "description": "Spending-currency units per 1 home-currency unit."
              },
              "referenceCost": {
                "type": "number"
              },
              "rates": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "date",
                  "source",
                  "url",
                  "fetchedAt",
                  "stale"
                ],
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Reference date of the rates."
                  },
                  "requestedDate": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "ecb-daily",
                      "ecb-sdmx",
                      "manual"
                    ]
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "fetchedAt": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "stale": {
                    "type": "boolean"
                  }
                }
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "name",
                    "method",
                    "homeCost",
                    "extraCost",
                    "extraPercent",
                    "effectiveRate",
                    "breakdown"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "method": {
                      "type": "string"
                    },
                    "homeCost": {
                      "type": "number"
                    },
                    "extraCost": {
                      "type": "number"
                    },
                    "extraPercent": {
                      "type": "number"
                    },
                    "effectiveRate": {
                      "type": "number"
                    },
                    "breakdown": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "atReference",
                        "markup",
                        "percentFee",
                        "fixedFees"
                      ],
                      "properties": {
                        "atReference": {
                          "type": "number"
                        },
                        "markup": {
                          "type": "number"
                        },
                        "percentFee": {
                          "type": "number"
                        },
                        "fixedFees": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              },
              "cheapest": {
                "type": "string"
              },
              "mostExpensive": {
                "type": "string"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "WarrantyValueCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "itemPrice": {
            "title": "Item price",
            "type": "number",
            "minimum": 0.01,
            "default": 800
          },
          "warrantyPrice": {
            "title": "Warranty price",
            "type": "number",
            "minimum": 0,
            "default": 120
          },
          "coverageYears": {
            "title": "Warranty covers (from purchase)",
            "type": "number",
            "minimum": 0.1,
            "maximum": 30,
            "default": 3,
            "x-unit": "years"
          },
          "overlapYears": {
            "title": "Of which already covered by legal / manufacturer warranty",
            "description": "E.g. 2 years of statutory warranty in the EU; set 0 if the warranty starts after it.",
            "type": "number",
            "minimum": 0,
            "maximum": 30,
            "default": 2,
            "x-unit": "years"
          },
          "annualFailureProbabilityPercent": {
            "title": "Chance of a failure per year",
            "description": "Your estimate; reliability differs a lot by product and brand.",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 5,
            "x-unit": "%/year"
          },
          "averageRepairCost": {
            "title": "Typical repair cost",
            "type": "number",
            "minimum": 0,
            "default": 250
          },
          "deductible": {
            "title": "Deductible per claim",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "claimApprovalPercent": {
            "title": "Chance a claim is approved",
            "description": "Exclusions (accidental damage, wear) often reduce real coverage.",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 80,
            "x-unit": "%"
          }
        },
        "required": [
          "itemPrice",
          "warrantyPrice"
        ]
      },
      "WarrantyValueCalculatorResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "currency",
              "verdict",
              "effectiveYears",
              "payoutPerClaim",
              "expectedClaims",
              "probabilityOfAnyClaim",
              "expectedPayout",
              "expectedNetValue",
              "returnPerCurrencyUnit",
              "breakEvenAnnualFailurePercent",
              "warrantyShareOfItemPricePercent"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "verdict": {
                "type": "string",
                "enum": [
                  "worth-it",
                  "marginal",
                  "not-worth-it"
                ]
              },
              "effectiveYears": {
                "type": "number",
                "description": "Coverage years not already covered by the legal/manufacturer warranty."
              },
              "payoutPerClaim": {
                "type": "number",
                "description": "min(repair cost, item price) − deductible, floored at 0."
              },
              "expectedClaims": {
                "type": "number",
                "description": "Expected number of approved claims."
              },
              "probabilityOfAnyClaim": {
                "type": "number",
                "minimum": 0,
                "maximum": 100,
                "description": "Percent chance of at least one approved claim."
              },
              "expectedPayout": {
                "type": "number"
              },
              "expectedNetValue": {
                "type": "number",
                "description": "expectedPayout − warranty price."
              },
              "returnPerCurrencyUnit": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Expected payout per unit of warranty price; null if the warranty is free."
              },
              "breakEvenAnnualFailurePercent": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Annual failure probability at which expected payout equals price; null if unreachable (above 100% or no payout possible)."
              },
              "warrantyShareOfItemPricePercent": {
                "type": "number"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}