Conventions
- All endpoints accept and return JSON (UTF-8).
- All POST endpoints require
Content-Type: application/json. - Authenticated endpoints rely on the
next-auth.session-tokencookie. - CHF amounts are returned as floating-point numbers (precision to 2 decimal places).
- Rates (effective, marginal) are returned as decimal fractions (0.15 = 15 %).
- Errors follow
{"error": "string", ...details}with appropriate HTTP status. - Rate limit: 60 requests/minute per IP for unauthenticated endpoints.
Public endpoints — calculators
/api/calculator/quickauth · none60-second tax + savings estimate (single user)
Request body
{
"canton": "ZH",
"communeBfs": 261,
"age": 40,
"grossSalary": 120000,
"civilStatus": "single" | "married"
}Sample response
{
"taxYear": 2026,
"preliminary": false,
"baseline": {
"totalTax": 18142.75,
"effectiveRate": 0.1511,
"marginalRate": 0.2622,
"federalTax": 4308,
"cantonalTax": 6845,
"communalTax": 8232
},
"commune": { "name": "Zürich", "bfs": 261, "steuerfuss": 119 },
"capacity": 290304,
"rangeLowSaving": 4623,
"rangeHighSaving": 6255,
"savingsBreakdown": {
"lppAmount": 18000,
"lppSaving": 4600,
"pillar3aAmount": 7258,
"pillar3aSaving": 1855,
"cashAvailable": 18000,
"cashSource": "estimated"
}
}/api/calculator/couplesauth · noneJoint married vs separate-singles comparison (marriage penalty)
/api/calculator/equityauth · noneRSU / stock-options vest-year tax with illiquidity discount
/api/calculator/3a-withdrawalauth · noneOptimal year-by-year 3a withdrawal schedule (brute-force solver)
/api/calculator/moveauth · noneRank 25 alternative cantons by annual saving net of amortised relocation cost
/api/newsletter/subscribeauth · noneDouble-opt-in newsletter signup (generates token, emails confirmation)
/api/newsletter/confirmauth · none (token)Confirms a pending newsletter subscription
/api/communesauth · noneAtlas data — list all communes with Steuerfuss, canton, BFS
Sample response
[
{ "canton": "ZH", "bfs": 261, "name": "Zürich", "steuerfuss": 119 },
{ "canton": "BE", "bfs": 351, "name": "Bern", "steuerfuss": 154 },
...
]/api/plan/demoauth · noneGenerate a free demo plan — no signup required
/api/plan/flowauth · noneMoney-flow diagram data for the explainer page
Authenticated endpoints (paid)
/api/baselineauth · sessionFull baseline tax computation with breakdown
/api/levers/allauth · sessionRank all tax-optimisation levers for the given profile
/api/levers/comboauth · sessionLPP + 3a combo lever with cashflow constraint
/api/levers/multi-yearauth · sessionDP solver for multi-year LPP buy-in distribution
/api/cantonal-moveauth · sessionSimulate tax impact of relocating to each canton
/api/plan/pdfauth · sessionGenerate PDF of the personalised plan
Upload endpoints
/api/uploadsauth · sessionMultipart upload of Lohnausweis or Vorsorgeausweis — AES-256-GCM encrypted at rest
/api/uploads/[id]auth · session (owner only)Download the encrypted blob
/api/extract/pension-certificateauth · sessionTrigger Claude-based extraction; AHV-redacted before LLM call
Payment
/api/checkoutauth · sessionCreate Stripe Checkout session (Card + TWINT)
Sample response
{ "url": "https://checkout.stripe.com/..." }Asset endpoints
/commune/[bfs]/opengraph-imageauth · nonePer-commune Open Graph PNG (1200×630) — for link previews on social
Errors
Common error responses:
400— Validation failed;errorcontains Zod flat error.401— Not signed in. Free endpoints never return this.402— Subscription required (for paid endpoints).429— Rate limit exceeded.500— Server error; please report tohello@optiqo.ch.501— Endpoint not yet wired (e.g. Stripe in development).
Versioning
We do not version the API URL today. The response shape is additive-only: we add fields, we do not rename or remove. If we ever need a breaking change we'll mount the new version at /api/v2/... and run both for at least 6 months.