Transparency
Your data format – openly documented
Your household data belongs to you. Kontoo stores it only on your device — and so you never have to feel locked in, this page documents exactly how every export file is structured. Everything is readable with standard tools: the JSON export with any text editor, the CSV file with any spreadsheet, the encrypted backup with freely available standard cryptography. Even if Kontoo disappeared tomorrow, you could still access everything.
1. JSON export (complete file)
“More menu → Export” produces a JSON file containing the whole household. Basic structure:
{
"meta": { "v": 25, "currency": "EUR", "name": "My household", … },
"people": [ { "id": "…", "name": "Alex" }, … ],
"income": [ { "id": "…", "name": "Salary", "v": 2400, "owner": "<person-id>", "freq": "monthly" }, … ],
"savings": [ …like income… ],
"personal": { "<person-id>": [ …items… ] },
"buckets": [ { "id": "…", "name": "Housing",
"split": { "mode": "income" | "even" | "fixed", "amounts": { "<person-id>": 500 } },
"groups": [ { "id": "…", "name": "Fixed costs", "items": [ …items… ] } ] } ],
"accounts": [ { "id": "…", "name": "Broker", "type": "depot|cash|property|loan|…", "value": 12000, "history": […] } ],
"goals": [ { "id": "…", "name": "Holiday", "target": 3000, "saved": 500, "contributions": […] } ],
"scenarios": […], "wishes": […], "classMap": { "<tag>": "need" | "want" }, "rules": { … }, "learn": { … }
}
An item has the fields id, name, v (amount), type
(amount = recurring amount, asset = purchase with cost/life
in years/buffer in %), freq
(monthly | quarterly | half | yearly | once), optional dueMonth (1–12),
tags, note, track + actuals (actual spending entries
{ts, amount, note}), assigned (envelope allocation per month) and
recurring (templates). The monthly value of a purchase is
cost / (life × 12) × (1 + buffer/100).
meta.v is the schema version. Kontoo reads all older versions of your exports forever
(internal migration on load) and warns — instead of silently discarding — if a file comes from a newer version.
2. CSV export (spreadsheet)
The CSV export re-imports losslessly and opens directly in Excel, LibreOffice or Numbers (UTF-8 with BOM; decimal and column separators follow your language). Columns:
| Column | Meaning |
|---|---|
| Section | income, savings, personal or bucket row |
| Bucket / Group / Person | where the item belongs in the household |
| Name, Type, Frequency | label, amount/purchase, payment rhythm |
| Amount/Price, Lifespan, Buffer, Monthly value | raw values + normalised monthly value |
| Tags, Class, Note | keywords, need/want (50/30/20), free text |
| Actual YYYY-MM | actual spending of the month (if tracking is on) |
3. Encrypted backup (.hbk)
The encrypted backup is a JSON “envelope” — it contains no plaintext whatsoever:
{ "format": "hbk1", "iter": 600000, "salt": "<Base64>", "iv": "<Base64>", "ct": "<Base64>" }
Decryption uses pure web standards (works in any browser and in Node.js, entirely without Kontoo):
- Derive the key: PBKDF2-SHA-256 with your password, the
saltanditeriterations → 256-bit key. - Decrypt: AES-256-GCM with the
ivonct→ the plaintext is the JSON export from section 1.
// Node.js ≥ 20 (WebCrypto), env = the parsed .hbk file
const b64 = s => Buffer.from(s, 'base64');
const km = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveKey']);
const key = await crypto.subtle.deriveKey({name:'PBKDF2', hash:'SHA-256', salt:b64(env.salt), iterations:env.iter},
km, {name:'AES-GCM', length:256}, false, ['decrypt']);
const plaintext = new TextDecoder().decode(
await crypto.subtle.decrypt({name:'AES-GCM', iv:b64(env.iv)}, key, b64(env.ct)));
4. Device sync (optional)
The optional sync transfers exactly the same hbk1 ciphertext as a blob. The blob address is derived
from your passphrase; the server never sees plaintext and knows neither your name nor an account. Without sync set
up, the app makes no network requests at all — verifiable in your browser’s network tab.
As of July 2026 · schema version 25. This page is updated whenever the format changes; older exports always remain importable.