Optiqo

API reference

Optiqo's HTTP endpoints. Public endpoints are open for any embed. Paid endpoints require an authenticated session.

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-token cookie.
  • 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

POST/api/calculator/quick

60-second tax + savings estimate (single user)

{
  "canton": "ZH",
  "communeBfs": 261,
  "age": 40,
  "grossSalary": 120000,
  "civilStatus": "single" | "married"
}
{
  "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"
  }
}
POST/api/calculator/couples

Joint married vs separate-singles comparison (marriage penalty)

POST/api/calculator/equity

RSU / stock-options vest-year tax with illiquidity discount

POST/api/calculator/3a-withdrawal

Optimal year-by-year 3a withdrawal schedule (brute-force solver)

POST/api/calculator/move

Rank 25 alternative cantons by annual saving net of amortised relocation cost

POST/api/newsletter/subscribe

Double-opt-in newsletter signup (generates token, emails confirmation)

GET/api/newsletter/confirm

Confirms a pending newsletter subscription

GET/api/communes

Atlas data — list all communes with Steuerfuss, canton, BFS

[
  { "canton": "ZH", "bfs": 261, "name": "Zürich", "steuerfuss": 119 },
  { "canton": "BE", "bfs": 351, "name": "Bern", "steuerfuss": 154 },
  ...
]
POST/api/plan/demo

Generate a free demo plan — no signup required

POST/api/plan/flow

Money-flow diagram data for the explainer page

Authenticated endpoints (paid)

POST/api/baseline

Full baseline tax computation with breakdown

POST/api/levers/all

Rank all tax-optimisation levers for the given profile

POST/api/levers/combo

LPP + 3a combo lever with cashflow constraint

POST/api/levers/multi-year

DP solver for multi-year LPP buy-in distribution

POST/api/cantonal-move

Simulate tax impact of relocating to each canton

POST/api/plan/pdf

Generate PDF of the personalised plan

Upload endpoints

POST/api/uploads

Multipart upload of Lohnausweis or Vorsorgeausweis — AES-256-GCM encrypted at rest

GET/api/uploads/[id]

Download the encrypted blob

POST/api/extract/pension-certificate

Trigger Claude-based extraction; AHV-redacted before LLM call

Payment

POST/api/checkout

Create Stripe Checkout session (Card + TWINT)

{ "url": "https://checkout.stripe.com/..." }

Asset endpoints

GET/commune/[bfs]/opengraph-image

Per-commune Open Graph PNG (1200×630) — for link previews on social

Errors

Common error responses:

  • 400 — Validation failed; error contains 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 to hello@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.

Continue to architecture →