PoliErgaleio Public Calculation API / MCP Server ================================================ Operator: PoliErgaleio (https://poliergaleio.com) Hosted on: Supabase Edge Functions Direct endpoint: https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api API version: 5.0.0 (2.0.0 introduces stricter calculator input requirements; 2.0.1 improves rent-vs-buy and mortgage tool guidance; 3.0.1 clarifies deposit units and stock-option approval) MCP endpoint: https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/mcp REST tools: https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools Schemas: https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/schemas Discovery: https://poliergaleio.com/.well-known/mcp.json Verify key: https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/whoami Browser key checker: https://poliergaleio.com/developers/verify OpenAPI: https://poliergaleio.com/poliergaleio-openapi.json ChatGPT Action 1 (calculators, 19 tools): https://poliergaleio.com/poliergaleio-chatgpt-actions.json ChatGPT Action 2 (saved calculations, 6 tools): https://poliergaleio.com/poliergaleio-chatgpt-user-actions.json Plain-text reference: https://poliergaleio.com/developers.txt Authentication -------------- Send your free API key in the x-api-key header. Get one at https://poliergaleio.com/profile. Omit the key to use the anonymous tier on stateless calculators (10/min, 50/day, 200/week). Free key tier: 60/min, 1,000/day, 5,000/week and unlocks saved-calculation tools. Keyed quota is enforced per key and against the whole account (same ceilings across every key owned, revoked keys included) — a 429 with scope "account" means new keys will not help. Key creation is capped at 3 new keys per 24 hours. Agents chaining several tools in one turn burn one call per tool — get a free key, the anonymous 10/min budget exhausts fast. Calculator request contract (API 2.0.0) --------------------------------------- - MCP and REST apply the same strict validation before calculation builders run. - Missing essential personal facts produce fieldErrors + a hint; examples are not defaults and must never be copied in as user facts. - Reuse information already supplied and ask only for missing essential inputs. - Successful calculator results report defaults and exclusions in structuredContent.assumptions and in the result text. Supplied inputs are not listed as defaults. Omitted state pension income is excluded; omitted optional fees/costs are disclosed. - Reconnect MCP clients or re-import the ChatGPT Action schema after this breaking release. Migration guide: https://poliergaleio.com/migrations/v2-0-0 API 2.0.1 tool guidance ----------------------- - Rent-vs-buy comparisons use only facts stated or confirmed for the current scenario; confirm before reusing financial values remembered from another chat or scenario. - After calculating, briefly disclose material modelling defaults and excluded costs. - Mortgage simulation is for repayment and rate scenarios, not document checklists or general mortgage information. - No request fields or calculation outputs changed; reconnect MCP clients or re-import ChatGPT Actions to refresh the updated tool descriptions. API 3.0.1 tool guidance ----------------------- - For rent-vs-buy, ask whether a unitless deposit (such as “20”) is EUR or percent before interpreting it or calculating. - For stock options, ask for user-confirmed approved-scheme status before any personalized gain, tax or savings figures; never infer approval. - No request fields or calculation outputs changed; reconnect MCP clients or re-import ChatGPT Actions to refresh the updated tool descriptions. API keys: limits, scopes, headers, rotation, errors -------------------------------------------------- - Limits: at most 10 active keys per account; creation capped at 3 new keys per 24h. - Scopes: every key carries scopes ["read", "write"]. Stateless calculators work anonymously; list/load/save/update/delete/compare of saved calculations require a key scoped to its own account. - Headers: x-api-key: YOUR_KEY on every request (never in URLs or client bundles). Optional x-guest-id separates anonymous quota behind one IP; optional X-Request-Id is echoed back — quote it when reporting an issue. - Rotation: create a new key at https://poliergaleio.com/profile, switch your app, then revoke the old one (immediate). The full secret shows once — copy it then; only the key prefix is stored. Rename keys anytime; revoke with confirmation. - Errors (JSON with error + requestId/guestId): 400 validation (fieldErrors + hint, check GET /schemas/{tool}); 401 missing/invalid key (verify at /developers/verify); 404 unknown tool/save; 429 rate limit (Retry-After header, scope key/account). Removed tools return 410, stale clients 426. Mobile / no desktop app ------------------------- ChatGPT Work MCP plugin: connect https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/mcp. Stateless tools are anonymous; saved-calculation tools request PoliErgaleio OAuth sign-in. Register ChatGPT's exact callback URL as a confidential client in Supabase Authentication > OAuth Apps and enter that client's ID/secret in the plugin configuration. ChatGPT Custom GPT (two Actions, sized for the 30-operation limit; the full openapi.json has 32 operations and will not import): 1. ChatGPT → Explore GPTs → Create. 2. Configure → Actions → Action 1: Import from URL: https://poliergaleio.com/poliergaleio-chatgpt-actions.json 3. Authentication Action 1: None (anonymous calculators tier). 4. Optional Action 2: Import from URL: https://poliergaleio.com/poliergaleio-chatgpt-user-actions.json Authentication Action 2: OAuth sign-in to PoliErgaleio. 5. Publish as "Anyone with the link" and share. Claude custom connector: 1. claude.ai or Claude app → Settings → Connectors → Add custom connector. 2. Paste https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/mcp 3. Add custom header x-api-key (optional; omit for anonymous). 4. Enable in a new chat and ask your question. Available tools --------------- - calculate_cyprus_income_tax Computes Cyprus personal income tax, GESY (National Health System) contributions, social insurance, and net (take-home) salary. Use for a Cyprus resident or non-resident employee or sole proprietor tax estimate. Before calling, confirm whether a stated salary is monthly or annual and confirm employment category (private, civil or self-employed); do not infer either. Supply gross annual salary in EUR (use explicit 0 for self-employed-only cases); self-employed calculations also need turnover and occupational category. Identify when 2026 tax rules are a default rather than a user-supplied year. Optional incomes, deductions, tax year and eligibility facts must come from the user. Article 8 exemptions are applied only after the existing eligibility checks; never infer exemption eligibility. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/calculate_cyprus_income_tax - calculate_cyprus_state_pension Estimates the Cyprus state (contributory) pension amount at retirement based on accumulated insurance units and contribution history. Use to estimate contributory Cyprus state pension entitlement and amount at retirement. Reuse supplied facts and ask only for missing essentials: mode and retirement age are always required; quick mode also needs current age, years worked and total insurance units; advanced mode needs the actual non-empty yearly contribution record. Ask for dependants or state the zero-dependants assumption before presenting a personalized result. Never infer contribution history. Units are per calendar year; a legacy year-to-units map is accepted. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/calculate_cyprus_state_pension - run_retirement_stress_test Runs a deterministic projection plus Monte Carlo simulation of a retirement savings plan to estimate the probability of not running out of money. Use for a retirement savings sustainability or portfolio-longevity projection. Reuse the user's ages, savings, contributions and planned monthly spending; ask for each missing essential input and never use example personal values. Values are EUR, contributions/drawdown are monthly, and ages are years. Omitted state pension means pension income is excluded. Returns, inflation, horizon and simulation count use disclosed modelling assumptions when omitted. Runs without seed are stochastic; a supplied seed makes identical inputs repeatable for the same implementation version. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/run_retirement_stress_test - calculate_cyprus_unemployment_benefit Estimates the Cyprus unemployment benefit amount and separately assesses entitlement when qualifying facts are supplied. Use to estimate the conditional Cyprus unemployment benefit amount and separately assess entitlement. Require the claim date, relevant-year insurable earnings and dependants to estimate an amount. Ask for qualifying insurance history, employee status, unemployment registration and availability for work before claiming entitlement; when those facts are missing, state that entitlement is undetermined and ask each missing follow-up question. Do not infer those facts from annual earnings. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/calculate_cyprus_unemployment_benefit - get_pension_reference_data Returns official Cyprus Social Insurance reference tables for a given year. Use to retrieve official Cyprus social-insurance reference figures for a user-specified calendar year. Reuse a year already given or ask for the missing year; do not assume which tax/reference year applies. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/get_pension_reference_data - parse_pension_statement Extracts structured contribution history from pasted Cyprus Social Insurance Services pension statement text. Use to extract supported contribution years and units from an actual pasted Cyprus Social Insurance statement. Do not fill gaps or create contribution records; report missing or unreadable details to the caller. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/parse_pension_statement - calculate_cyprus_capital_gains Calculates Cyprus Capital Gains Tax on an immovable property disposal, including CPI indexation of the acquisition cost, lifetime first-disposal exemptions, the 20% CGT rate and the 0.4% property transfer fee. Use for Cyprus capital-gains tax on an immovable-property sale. Obtain the actual acquisition price and year, sale price and property type; ask for missing essentials. Currency defaults to EUR only as a modelling convention. Omission of isFirstDisposal means no lifetime exemption is claimed; do not infer eligibility. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/calculate_cyprus_capital_gains - simulate_mortgage Simulates a Cyprus mortgage: monthly payment, outstanding principal, interest, and best/worst/varied variable-rate scenarios. Supports fixed then variable periods, monthly overpayments, lump sums and interest-only loans. Interest-only principal remains due at maturity. KFIK and cash-collateral configurations are not available through this API. Use only to estimate a Cyprus mortgage payment, amortization, overpayments or rate scenarios. Require the actual loan amount, term, selected fixed-period length and the rate inputs for each phase that applies; never substitute example loan terms or rates. EUR amounts and annual percentage rates are expected. For document checklists or general mortgage information, do not call the calculator; answer or research directly without narrating tool selection. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/simulate_mortgage - calculate_stock_options Compares the Cyprus tax on an employee stock option exercise under the 2025 benefit-in-kind rules (progressive income tax) and the 2026 approved-scheme rules (flat 8% within caps), including the 2x salary annual cap and the €1m rolling 10-year lifetime cap. Use to compare Cyprus tax on an employee's share-option exercise. Require actual annual salary, share count, exercise and market prices, and user-confirmed approved-scheme status before any personalized calculation or call. If status is missing or unclear, ask one concise question first; do not infer it, call with an assumed status, or present option gain, tax, savings, or other personalized option figures before confirmation. Amounts are EUR and per-share prices are EUR/share. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/calculate_stock_options - compare_rent_vs_buy Compares buying a home against renting and investing the difference over a multi-year horizon, modelling the mortgage, purchase and selling costs, maintenance, rent inflation and investment returns, and reporting the breakeven year. Use only to compare buying a Cyprus home with renting over a stated horizon. Use financial facts stated or confirmed for this scenario. Do not carry values from another chat or scenario into the calculation until the user explicitly confirms the relevant values. Ask for any missing core term: property price, monthly rent, down payment and whether it is EUR or percent, mortgage rate, and mortgage term. If the deposit is given without a unit (for example, '20'), ask whether it means EUR or percent before interpreting, converting, deriving a loan amount, or calling this tool. Never infer the unit from context. Optional growth, inflation and return assumptions require user acceptance when not stated. After calculating, briefly summarize every material modelling default used (including defaults supplied in the call) and costs excluded or treated as zero; use structuredContent.assumptions for defaults and exclusions returned by the API. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/compare_rent_vs_buy - calculate_ev_savings Compares the annual running cost of an electric car against a petrol/diesel car in Cyprus, splitting charging between spare solar production, home grid electricity and public chargers, and reporting yearly savings and payback on the extra purchase cost. Use to compare EV and petrol/diesel running costs. Ask for actual annual distance, vehicle consumption, fuel price, charging shares and tariffs for each active charging source; when solar charging is selected, require available spare solar production. Reuse supplied facts and ask only for missing essentials. Omitted extra purchase cost means no payback estimate. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/calculate_ev_savings - compare_investment_vehicles Compares a taxable investment (option 1) against a tax-deductible vehicle such as a provident fund or life-insurance savings plan (option 2), matching the true out-of-pocket cost of both and applying entry/exit fees, AUM fees, commission and exit tax. Use to compare taxable investment with a tax-deductible investment vehicle. Require the annual contribution, the user's tax-relief rate and option 2's management fee. Reuse supplied facts and ask only for missing essentials. Disclosed forecast returns and optional omitted fee/tax inputs are modelling assumptions; do not invent personal contribution amounts. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/compare_investment_vehicles - plan_monthly_budget Builds a monthly budget from a list of expenses at any frequency: normalises everything to a monthly basis, tiers each item (T1 fixed essentials, T2 variable essentials, T3 lifestyle, T4 discretionary), and returns disposable income, savings rate, the biggest optimisable leak and a surplus projection. Use to summarize a household's actual monthly net income and expense items. Ask for the actual expense ledger or item amounts; never fabricate expenses. Entries without a frequency are treated as monthly and disclosed. Omitted investment contributions are treated as zero and disclosed. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/plan_monthly_budget - analyze_portfolio Analyses an investment portfolio from its transaction history: net invested capital, current value, absolute return, chain-linked time-weighted return (TWR), CAGR on initial invested capital, per-sub-portfolio breakdown and allocation shares, with multi-currency support via per-transaction FX rates. Use to analyze actual portfolio ledgers or current holdings and valuation data. Require real transactions/holdings and a current valuation for each holding; require FX rates where currencies differ. TWR needs a valuation immediately before each later external cash flow and at the end date; report null with its reason when absent. Equal and opposite same-day movements between sleeves in the base currency net to an internal transfer for combined TWR; confirm both legs before using that treatment. Missing acquisition cost may use the disclosed performance-from-today behavior. Never create transactions or holdings records. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/analyze_portfolio - simulate_life_insurance_flush Simulates a Cyprus unit-linked life insurance policy year by year (monthly compounding, AUM and fixed fees, age-based mortality cost, 5% bid/ask spread when premiums are self-funded from the policy) and computes the tax-free 'safe limit' for partial surrenders under the 2026 rules, the tax due on any excess, and IRR with and without the premium tax relief. Use to model withdrawals from an existing Cyprus life-insurance policy. Require actual policy terms, tax rate, historical policy records and future premium/withdrawal projections; ask for missing items and never generate policy history. Forecast returns are assumptions and must be identified as such. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/simulate_life_insurance_flush - audit_mortgage_statement Forensic audit of a mortgage statement: replays the loan day by day and grid-searches the bank's interest convention (Actual/365, Actual/360 or Actual/Actual day count; monthly, semi-annual or annual capitalisation; exact or 2-decimal daily rounding), ranks candidates by RMS error against the bank's own running balance, and reports the interest overcharge implied by the best fit. Use to audit an actual mortgage statement. Require statement rows, at least one usable interest rate and balance checkpoints sufficient to fit the calculation. Reuse only extracted facts; inferred day-count and capitalization conventions are estimates, not confirmed bank terms. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/audit_mortgage_statement - list_saved_calculations (requires API key) Lists metadata of an authenticated user's saved calculations by calculator type. Use so an agent can help a user find a previously saved scenario before loading it. Never returns encrypted contents. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/list_saved_calculations - load_saved_calculation (requires API key) Loads one saved calculation belonging to the authenticated API key's user. Use after list_saved_calculations to retrieve full details of a specific saved scenario. Locked saves return locked: true and no contents. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/load_saved_calculation - save_calculation (requires API key) Saves a calculator scenario to the authenticated user's stored history. Use when the user asks to save, keep or remember a scenario. Stores the inputs unencrypted (with server-computed results) so list/load can return them later. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/save_calculation - update_calculation (requires API key) Renames a saved calculation and/or replaces its inputs (recomputing results). Use when the user asks to rename a save or re-run it with different inputs. Encrypted saves stay locked. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/update_calculation - delete_saved_calculation (requires API key) Deletes one saved calculation belonging to the authenticated API key's user. Use when the user asks to delete or forget a saved scenario. Works on encrypted saves too. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/delete_saved_calculation - compare_saved_calculations (requires API key) Diffs two saved calculations (inputs and results) field by field. Use for 'compare my mortgage before/after' style questions instead of reconstructing the diff from two loads. Encrypted sides report locked instead of contents. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/compare_saved_calculations - parse_portfolio_csv Turns pasted broker CSV or ledger text into analyze_portfolio transactions. Use to extract supported transactions from actual pasted broker CSV or ledger text. Require the source text and actual base currency, report missing/unreadable values, and never create transactions or assume EUR. Output rows can be sent to analyze_portfolio. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/parse_portfolio_csv - parse_mortgage_statement_text Turns pasted bank mortgage-statement rows into audit_mortgage_statement inputs. Use to extract supported fields from actual mortgage statement text. Report missing fields and do not create or infer statement rows. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/parse_mortgage_statement_text - optimize_company_director_remuneration Optimizes Cyprus LTD company-director remuneration: finds the salary-vs-dividend split with maximum net cash in pocket. Use for a Cyprus LTD director remuneration comparison. Require company profit/revenue, deductible expenses, tax year and domicile status; reuse supplied data and request missing essentials. The default €2,000 corporate administration cost is a disclosed modelling assumption. https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/optimize_company_director_remuneration Security -------- - API keys are scoped to your PoliErgaleio account. Never share them or put them in URLs. - Saved calculations are encrypted client-side and the API never decrypts them: load_saved_calculation returns full data only for unencrypted saves owned by your key (encrypted saves return locked: true — open those on poliergaleio.com). - All traffic is HTTPS only. Verification ------------ - Discovery manifest: https://poliergaleio.com/.well-known/mcp.json - Tool list: GET https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools - Key check: GET https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/whoami with x-api-key - Browser checker: https://poliergaleio.com/developers/verify - OpenAPI spec (full, 32 ops — Copilot/Poe/REST): https://poliergaleio.com/poliergaleio-openapi.json - ChatGPT Action 1 (calculators, 19 tools): https://poliergaleio.com/poliergaleio-chatgpt-actions.json - ChatGPT Action 2 (saved calculations, 6 tools): https://poliergaleio.com/poliergaleio-chatgpt-user-actions.json Staying up to date ------------------ - Every response carries the current API version in the X-API-Version header and the apiVersion body field. - Check GET https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/version for the current version and changelog (cacheable, ETag-supported). - MCP clients (Claude, Cursor) pick up new tools automatically on reconnect — no reinstall needed. - ChatGPT Custom GPTs cache the Action schema; re-import https://poliergaleio.com/poliergaleio-chatgpt-actions.json (and https://poliergaleio.com/poliergaleio-chatgpt-user-actions.json if used) in the GPT editor to pick up new tools. - Breaking changes are announced in advance with Deprecation and Sunset headers plus a plain-language notice field in the response body. - GET https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/migrations/latest returns the newest migration guide and its section anchors: { "apiVersion": "5.0.0", "version": "5.0.0", "url": "https://poliergaleio.com/migrations/v5-0-0", "sections": { "whatChanged": "https://poliergaleio.com/migrations/v5-0-0#what-changed", "toolChanges": "https://poliergaleio.com/migrations/v5-0-0#tool-changes", "updateSteps": "https://poliergaleio.com/migrations/v5-0-0#update-steps" }, "anchors": { "...": "same three links, convenience alias" }, "allGuides": [ { "version": "1.7.0", "url": "...", "summary": "...", "sections": { } } ], "migrationsIndex": "https://poliergaleio.com/migrations", "minimumClientVersion": "1.0.0" } Cacheable: send If-None-Match with the returned ETag for a 304. Copy-paste curl: # 1. fetch and keep the ETag curl -sD - https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/migrations/latest -o latest.json | grep -i -E '^(etag|cache-control)' # ETag: "5.0.0-a1b2c3d4" # Cache-Control: public, max-age=300, stale-while-revalidate=86400 # 2. revalidate later — returns 304 with an empty body while unchanged curl -s -o /dev/null -w '%{http_code}\n' \ -H 'If-None-Match: "5.0.0-a1b2c3d4"' \ https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/migrations/latest MCP client pseudo-code (any language): const res = await fetch(base + '/migrations/latest', { headers: storedEtag ? { 'If-None-Match': storedEtag } : {}, }); if (res.status === 304) return cachedGuide; // still current const guide = await res.json(); // { version, url, sections, allGuides } storedEtag = res.headers.get('ETag'); openInBrowser(guide.sections.updateSteps); Validating error payloads automatically --------------------------------------- JSON Schema 2020-12 for the shared lifecycle error body: Download: https://poliergaleio.com/poliergaleio-lifecycle-error.schema.json Live: GET https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/schemas/errors/lifecycle (cacheable, ETag-supported) curl -s https://poliergaleio.com/poliergaleio-lifecycle-error.schema.json -o lifecycle-error.schema.json npx ajv-cli validate -s lifecycle-error.schema.json -d error-body.json --spec=draft2020 Lifecycle error bodies (426 and 410) ------------------------------------ Both share the same parseable shape: error, code, tool, status, replacement, migrationGuide and migrationSections (#what-changed / #tool-changes / #update-steps). 410 Gone — the tool passed its sunset date: { "error": "Tool old_tool was removed on 2026-01-01 and is no longer available.", "code": "tool_gone", "tool": "old_tool", "status": "removed", "sunset": "2026-01-01", "replacement": "new_tool", "notes": "See https://poliergaleio.com/migrations/v5-0-0", "migrationGuide": "https://poliergaleio.com/migrations/v5-0-0", "migrationSections": { "whatChanged": "...#what-changed", "toolChanges": "...#tool-changes", "updateSteps": "...#update-steps" }, "availableTools": ["calculate_cyprus_income_tax", "..."], "hint": "Call new_tool instead, then follow ...#update-steps.", "requestId": "...", "guestId": null } Headers: Deprecation: true, Sunset: , X-Migration-Guide, Link. Reproduce it: curl -s -X POST https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/ \ -H 'Content-Type: application/json' -d '{}' | jq '{code, tool, replacement, migrationSections}' 426 Upgrade Required — you sent an X-Client-Version below the minimum: { "error": "Client version 0.9.0 is no longer supported. Minimum supported version is 1.0.0.", "code": "upgrade_required", "tool": "calculate_cyprus_income_tax", "status": "stable", "replacement": null, "clientVersion": "0.9.0", "minimumClientVersion": "1.0.0", "apiVersion": "5.0.0", "updateUrl": "https://poliergaleio.com/developers", "migrationGuide": "https://poliergaleio.com/migrations/v1-2-0", "migrationSections": { "whatChanged": "...#what-changed", "toolChanges": "...#tool-changes", "updateSteps": "...#update-steps" }, "migrationPath": [ { "version": "1.2.0", "url": "...", "summary": "...", "sections": { } } ], "latestMigrationGuide": "https://poliergaleio.com/migrations/v5-0-0", "migrationNotes": ["Start here: ...", "..."], "requestId": "...", "guestId": null } tool/status/replacement are null when the request path does not name a tool. Reproduce it: curl -s -X POST https://gpkpjfdlbsuyevfdsnbq.supabase.co/functions/v1/public-api/tools/calculate_cyprus_income_tax \ -H 'Content-Type: application/json' -H 'X-Client-Version: 0.9.0' \ -d '{"salaryIncome": 45000}' | jq '{code, clientVersion, migrationGuide, migrationPath}' One code path handles both: if (res.status === 410 || res.status === 426) { const err = await res.json(); // validates against the schema above console.error(err.error, '->', err.replacement ?? err.migrationGuide); openInBrowser(err.migrationSections.updateSteps); }