Skip to main content

yaybo

yaybo

Look up a Danish address, see what the land register (Tingbogen) holds on it, and keep the results in a local DuckDB database you can browse, query and export from your terminal.
PyPI - Version

Python versions prek uv Ruff ty tox CI Release to PyPI


Install

uv tool install yaybo    # or: pip install yaybo

Python 3.10 or newer. uvx yaybo runs it without installing anything.

Quickstart

yaybo                                       # open the TUI
yaybo fetch "Prøvegade 1, 9999 Prøveby"     # fetch one address
yaybo --version                             # which version is installed

Results go to out/tinglysning.duckdb. Looking the same address up again replaces its rows rather than adding a second copy.

Where the data comes from

source what it provides login needed
tinglysning.dk owners, mortgages and charges, easements, past transfers partly
Boligsiden sale prices, price per m², and the BBR record (year built, rooms, walls, heating) no
Danmarks Statistik what each kind of realkredit loan cost month by month, used to read a bare interest rate as an F3 or a fixed loan no
DAWA address lookup and validation while you type no

Most of it is available without logging in. Logging in with MitID adds owners' dates of birth, everyone named on each mortgage, and the history of previous owners.

The TUI

Run yaybo with no arguments. Six screens, all reading the same database:

screen key what it is for
Library l everything you have fetched, searchable offline
Search / find an address, see what the register holds at it
Queue b fetch many properties in the background
Nøgletal k figures across a set of properties
SQL s query the database directly
Property enter one property in full

Library is where it opens. Each row shows how stale it is, its valuation, debt and loan-to-value, and whether it was fetched while logged in. Sort by any column with o, filter with name:value, tick rows with space and act on the lot. enter opens a property, f re-fetches, e exports.

Search resolves your typing against DAWA, then asks the register which properties actually sit at that address – one for a rented block, over a hundred for a block of owner-occupied flats. Tick the ones you want and press f to hand them to the queue.

Property shows one property, tab by tab, read back out of the database, so it is instant and works offline:

  • Oversigt – what it is, who owns it, what it is worth, what it owes
  • Ejere · Hæftelser · Servitutter · Parter · Handler – the tables in full, with each charge's interest terms and the loan product it implies
  • Forløb – sales, transfers, mortgages, easements and valuations on one timeline. The register keeps these as four separate lists
  • Kurve – price per square metre over time
  • Bygning – the BBR record
  • Dokument – the signed attest itself

Queue takes what Search hands it and fetches in the background, with a progress bar and per-row status. Pause with space. Anything already fetched is already saved, so a lapsed login partway through costs you nothing.

Nøgletal answers questions about a set of properties rather than one: median price per m² by floor, valuations by building, owners by postcode. Narrow the set with dropdowns filled from the database, then pick a figure. Nothing is fetched – it describes only the properties you already hold.

SQL runs read-only queries against the whole database, with eight examples ready to load and edit. ctrl+R runs, ctrl+E exports the result.

Press ctrl+L anywhere to log in with MitID, or to log out.

On the command line

yaybo                              # the TUI
yaybo --version                    # the installed version
yaybo fetch ADDRESS [ADDRESS ...]  # look addresses up and store them
yaybo export                       # write what is stored to a spreadsheet
yaybo login --user YourMitIDUserID
yaybo status                       # is the session good, and for how long
yaybo keepalive [MINUTES]          # hold it open without another trip to the phone
yaybo backfill                     # re-derive stored tables, fetching nothing
yaybo logout

fetch takes several addresses at once and pauses between them. One that cannot be resolved is reported and skipped; the rest still get fetched.

yaybo fetch "Prøvegade 1, 9999 Prøveby" "Prøvevej 2, 9999 Prøveby"

Useful fetch options:

option what it does
--format LIST duckdb (default), csv, xlsx, or a comma-separated list
--limit N most properties to fetch (default 25, 0 for no limit)
--anonymous ignore any cached session and use only the public lookup
--delay SECONDS pause between fetches (default 1.0)
--outdir DIR where results go (default out/, which is git-ignored)
--no-boligsiden skip sale prices, BBR data and equity
--no-laantype skip estimating each charge's loan type
--keepalive [MIN] hold the session open afterwards (default 60)

--debug and --db PATH work before or after the subcommand.

export writes what is already stored, rather than what was just fetched:

yaybo export                                    # everything -> exports/*.xlsx
yaybo export --format csv --name priser --query "SELECT ..."
yaybo export --query-file report.sql --name rapport

It prints the written path on stdout and nothing else, so file=$(yaybo export) gives you the file.

fetch and the TUI share the same pipeline, so they cannot drift apart.

backfill rebuilds every table derived from a stored document – charges, easements, the people named on them, previous owners – with no login and no requests to the register. Run it after upgrading, when a reader has improved.

The data

One DuckDB file, twelve tables, keyed on the property.

erDiagram
    ejendomme ||--o{ ejere : owns
    ejendomme ||--o{ haeftelser : "charged with"
    ejendomme ||--o{ servitutter : "burdened by"
    ejendomme ||--o{ bygninger : "built on"
    ejendomme ||--o{ handelshistorik : "sold as"
    ejendomme ||--o{ adkomsthistorik : "transferred as"
    ejendomme ||--o| attester : "documented by"
    ejendomme ||--o{ dokument_parter : "named on"
    haeftelser ||--o{ underpant : "pledged as"
    haeftelser ||--o{ dokument_parter : names
    servitutter ||--o{ dokument_parter : names
    adkomsthistorik ||--o{ adkomsthistorik_ejere : names

    ejendomme {
        varchar uuid PK
        varchar adresse
        bigint ejendomsvurdering_dkk
        bigint samlet_gaeld_dkk "derived"
        bigint frivaerdi_dkk "derived"
        double belaaningsgrad_pct "derived"
        boolean beriget "fetched while logged in"
    }
    ejere {
        varchar ejendom_uuid PK, FK
        bigint nummer PK
        varchar navn
        date foedselsdato "login only"
        varchar cvr
    }
    haeftelser {
        varchar ejendom_uuid PK, FK
        varchar dokument_uuid PK
        varchar dokument_version PK
        bigint prioritet
        bigint hovedstol_dkk
        double rentesats_pct
        varchar laantype_estimat "estimated"
    }
    servitutter {
        varchar ejendom_uuid PK, FK
        varchar dokument_uuid PK
        varchar dokument_version PK
        varchar dokumenttype
        varchar tekst
    }
    dokument_parter {
        varchar ejendom_uuid PK, FK
        varchar dokument_uuid PK, FK
        varchar dokumentart PK
        varchar rolle PK
        bigint nummer PK
        varchar navn
        date foedselsdato "login only"
    }
    underpant {
        varchar ejendom_uuid PK, FK
        varchar haeftelse_uuid PK, FK
        varchar rettighed_uuid PK
        bigint beloeb_dkk
    }
    handelshistorik {
        varchar ejendom_uuid PK, FK
        varchar registrering_id PK
        date dato
        bigint beloeb_dkk
        bigint pris_pr_m2
    }
    bygninger {
        varchar ejendom_uuid PK, FK
        varchar bygning_nr PK
        bigint opfoerelsesaar
        bigint boligareal_m2
        varchar varmeinstallation
    }
    adkomsthistorik {
        varchar ejendom_uuid PK, FK
        bigint post_nummer PK
        date dato
        bigint koebesum_dkk
    }
    adkomsthistorik_ejere {
        varchar ejendom_uuid PK, FK
        bigint post_nummer PK, FK
        bigint nummer PK
        varchar navn
    }
    attester {
        varchar ejendom_uuid PK, FK
        varchar dokument "as signed"
        json dokument_json "queryable"
    }
    rentestatistik {
        varchar maaned PK
        varchar rentfix_kode PK
        varchar laantype
        double effektiv_rente_pct
    }
table one row per needs login
ejendomme property, with valuation, debt and equity no
ejere current owner no
haeftelser mortgage or charge, with interest terms and estimated loan type no
servitutter easement, and what it is about no
dokument_parter person named on a document, with their role and date of birth or CVR yes
underpant deed pledged on in its own right no
handelshistorik recorded sale, with price per m² no
bygninger building in the BBR record no
adkomsthistorik past transfer, with what was paid yes
adkomsthistorik_ejere person named in one of those transfers yes
attester the property's whole register document, signed and as JSON yes
rentestatistik month of DST realkredit rates no

rentestatistik is not about any one property. It is the rate series laantype_estimat was matched against, kept so an estimate can be checked.

The full column-level schema is in schema.dbml, generated from the code so it cannot fall behind it. Paste it into dbdiagram.io for a browsable diagram.

Every table has a primary key, so a row is identifiable and a re-run replaces rather than duplicates. Relationships are drawn above but not enforced: DuckDB cannot add a foreign key to an existing table, so enforcing them would leave every database created before this version permanently unable to catch up.

Exporting

Press e on any screen, pass --format to fetch, or run yaybo export against what is already stored.

format shape
DuckDB one table each, as they already are
Excel one sheet per table, header row frozen
CSV one file per table, in a folder named after the address

Column order follows the schema, so the same table exported twice has the same columns in the same places.

Using it with an AI agent

The TUI is for people. Agents should use the CLI, which is non-interactive apart from the login.

Install the plugin (no clone needed)

In Claude Code – terminal or desktop – type:

/plugin marketplace add kiliantscherny/yaybo
/plugin install yaybo@yaybo

That is the whole setup. It works from any folder, and gives the agent the CLI reference, the data model and the schema. The only thing you need on your machine is uv – it installs Python itself, so that is not a separate prerequisite:

curl -LsSf https://astral.sh/uv/install.sh | sh          # macOS and Linux
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"   # Windows

Then just ask, in plain language:

What does the land register hold on Prøvegade 1 and Prøvevej 2 in 9999 Prøveby? Put it in a spreadsheet.

The agent fetches with yaybo fetch, queries the database, and hands back an Excel file. Fetched data lands in out/ in whatever folder you are working in, so it is worth making one and staying in it:

mkdir ~/property-lookups && cd ~/property-lookups

Or clone the repository

Cloning gets you the source. To use the skill from a clone without installing it, point Claude Code at the plugin directory:

git clone https://github.com/kiliantscherny/yaybo.git
cd yaybo
claude --plugin-dir ./plugin

Either way the agent has:

file what it gives the agent
AGENTS.md the project, its rules, and what not to do (CLAUDE.md symlinks to it)
schema.dbml every table, column, type and key, generated from the code
plugin/skills/danish-property-records/ the CLI, the data model and worked queries, loaded only when needed

What you are taking on

Logging in means logging in as you, to a government register, with MitID.

mitid-client

The MitID login is a separate library: mitid-client. It knows nothing about property – it is a Python stand-in for MitID's JavaScript core client, the NemLog-in broker that fronts the Danish public sector, a store for keeping a login's cookies between runs, and two ways of showing a login to whoever is doing it: a few lines on stderr, or a Textual screen.

from mitid.brokers import nemlogin
from mitid.ui.tui import MitIDLoginScreen

session = nemlogin.new_session()
result = await self.push_screen_wait(
    MitIDLoginScreen(partial(nemlogin.log_in, session, START_URL))
)

Point it at any NemLog-in-protected URL and it returns the session cookie that URL was guarding. It installs as a dependency of this, so there is nothing to do about it. It is worth knowing about separately because the login is the reusable half.

Contributing

See CONTRIBUTING.md. Releases are described in RELEASING.md, and changes in CHANGELOG.md.

MIT licensed.

Metadata

Release files for yaybo 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for yaybo 1.0.0
File Size Uploaded
yaybo-1.0.0.tar.gz 165.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for yaybo 1.0.0
File Interpreter ABI Platform
yaybo-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 316.4 kB

Release files / yaybo-1.0.0.tar.gz

Download URL yaybo-1.0.0.tar.gz
Size 165.7 kB
Tags Source
SHA-256 checksum
How to use checksums
a42fd8c68443d51b82b471139be913b159a9e2dcea4bc759e5dbbb0385cf4c96
BLAKE2b-256 checksum
How to use checksums
ae048c6c3c3fc975e162b2dcfa7dd400d1988e8c350c4f4ec14838c2db6da03a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / yaybo-1.0.0-py3-none-any.whl

Download URL yaybo-1.0.0-py3-none-any.whl
Size 150.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f34c1db3ec3728ebb24a5c648e6c2a9d978ff60f208aad6e69ec9af74a59267b
BLAKE2b-256 checksum
How to use checksums
9a49055ba50a930c6abdec6ddd7ddfe94a729552a84702f7b11d6d79821d3faf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

1.2.0

2 release files

1.1.0

2 release files

This release

1.0.0 This release

2 release files

0.2.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page