Skip to main content

otb-kbo-opendata-pyclient

CI Release Coverage PyPI Python versions Licence

Python client for the SFTP server of the Belgian Crossroads Bank for Enterprises (KBO/BCE), which publishes daily KBO Open Data ZIP files. Use it as a library or through the kbo-opendata command.

Features

  • Discover the latest publication, or look one up by index or by date
  • List everything on the server, filter by index or date range, and spot skipped indices
  • Download by filename, index, date, or "the latest", choosing update files, full files, or both
  • Sync a destination with the server, fetching only the files it does not already hold
  • Write to a local directory or straight to S3, streaming rather than buffering whole files
  • Host-key verification against known_hosts by default
  • Fully typed, with a py.typed marker

Installation

pip install otb-kbo-opendata-pyclient

Getting access

KBO Open Data is free but not anonymous. Two steps are needed before this client can connect:

  1. Register on the KBO Open Data portal and accept the licence. This alone gives you manual downloads through the website.
  2. Request SFTP access separately, in advance, by emailing kbo-bce-webservice@economie.fgov.be. Portal registration does not grant it.

The credentials you receive are what this package uses. There is no test or sandbox server.

Credentials

The client needs the username and password issued by KBO. The library accepts them directly; the CLI reads them from the environment.

Variable Purpose
KBO_OPENDATA_USERNAME SFTP account name, which also names the remote directory
KBO_OPENDATA_PASSWORD SFTP password

About the published files

KBO snapshots its database daily and publishes two ZIP files per snapshot:

  • a full file — every active registered entity and establishment unit at the moment of the snapshot
  • an update file — the differences between the last full file and the one before it

Files are kept for 31 days only. Anything older is gone from the server, so list_all(), missing_indices() and date lookups only ever see roughly the last month. Download the full file first, then keep up with either update files or a periodic full file.

The XXXX in a filename is the ExtractNumber, incremented by one per publication. It is not a date: KBO can skip a calendar day, which is why missing_indices() reports gaps in the sequence rather than missing dates.

What is inside a ZIP

This package downloads and stores the ZIP files; it does not extract or parse them. Each archive holds CSV files — meta.csv, code.csv, enterprise.csv, establishment.csv, denomination.csv, address.csv, contact.csv, activity.csv and branch.csv — joined on the enterprise number, establishment number or branch id. The CSV conventions are a comma delimiter, double-quoted text, a full stop as decimal point, and dd-mm-yyyy dates.

meta.csv carries SnapshotDate, ExtractTimestamp, ExtractType (full or update), ExtractNumber and the format Version, which is the reliable way to confirm what an archive actually contains.

An update archive splits each table into a _delete and an _insert file. Applying one means deleting every row for the listed entity numbers, then inserting the rows from the _insert file — the insert file repeats all current rows for a changed entity, not only the changed ones. The files carry no history: only the current state of active entities.

Documentation

  • Cookbook — file structure, CSV field descriptions and the update procedure
  • Data catalogue, the reusable data fields — no English version exists, only Dutch and French
  • KBO Open Data page

Library usage

import datetime as dt

from kbo_opendata import KboOpenDataClient, LocalDestination, S3Destination

with KboOpenDataClient(username="...", password="...") as client:
    latest = client.latest()
    print(latest.update_filename, latest.full_filename)

    pair = client.get_by_index(423)  # None when the index was skipped
    pair = client.get_by_date(dt.date(2026, 8, 7))

    client.download_latest(LocalDestination("./downloads"))
    client.download_index(423, S3Destination("my-bucket", "kbo/"), kinds=["full"])

KboOpenDataClient.from_env() builds the same client from the environment variables above.

Queries

Method Returns
latest() The pair with the highest index, or None
latest_n(count) The most recent pairs, newest last
get_by_index(index) The pair for that index, or None
get_by_date(date) The pair for that date, or None
list_all() Every pair, oldest first
list_by_index_range(start, end) Pairs within inclusive index bounds
list_by_date_range(start, end) Pairs within inclusive date bounds
missing_indices() Indices skipped between the lowest and highest present
exists(filename) Whether the server holds that file
stat(filename) Size and modification time
catalogue(refresh=False) The whole listing as a Catalogue, fetched once and reused
refresh() Fetch the listing again and return it

A KboFilePair carries update_filename and full_filename, either of which is None when the server holds only one of the two. Both are bare filenames, without a path.

The listing is fetched on first use and cached, so repeated queries cost nothing; call refresh() to pick up files published since. Use the client as a context manager, as above, or call close() to release the connection and drop the cached listing. The store property exposes the underlying transport, which is what the test suite substitutes.

Downloads

Method Downloads
download(filenames, destination) The named files
download_index(index, destination) The files for one index
download_date(date, destination) The files for one date
download_latest(destination) The most recent files

Every download method accepts overwrite (defaults to False, so existing files are skipped) and dry_run. The index, date and latest variants also accept kinds, which defaults to update and full; download takes explicit filenames, so it has no kinds. They all return a DownloadResult whose written, skipped, missing and planned tuples say what happened to each file.

The index and date variants raise RemoteFileNotFoundError when nothing matches; download reports unknown names as missing instead, so a multi-file request always reports on every name.

S3

S3Destination("my-bucket", "kbo/", extra_args={"ServerSideEncryption": "AES256"})

A boto3 client is created from the ambient AWS configuration on first use. Pass client= to supply one built from your own session, and extra_args to forward parameters to the upload.

Command line usage

export KBO_OPENDATA_USERNAME=...
export KBO_OPENDATA_PASSWORD=...

kbo-opendata --version
kbo-opendata show-latest
kbo-opendata check-index 0423
kbo-opendata check-date 2026-08-07
kbo-opendata list-files --from-index 400 --to-index 425
kbo-opendata list-files --from-date 2026-08-01 --to-date 2026-08-07 --latest 10
kbo-opendata list-gaps
kbo-opendata check-credentials

kbo-opendata download-latest --dest ./downloads
kbo-opendata download-indexes 421 423 --dest ./downloads --kind full
kbo-opendata download-dates 2026-08-03 2026-08-07 --s3-bucket my-bucket --s3-prefix kbo/
kbo-opendata download KboOpenData_0423_2026_08_07_Full.zip --dest ./downloads

kbo-opendata sync --dest ./downloads

sync downloads the files on the server that are not yet at the destination. Presence is decided on the filename alone, so a file whose content changed on the server is not fetched again unless --overwrite is given. It honours --kind, so it collects both kinds unless told otherwise, and it ignores remote entries that are not KBO data files. Its text output lists what it transfers and then counts what was already there, rather than naming every file again:

KboOpenData_0425_2026_08_07_Full.zip: written -> ./downloads/KboOpenData_0425_2026_08_07_Full.zip
848 already present, 1 written (412.7 MB)

Dates are accepted as 2026-08-07, 2026_08_07 or 20260807, on every supported Python version.

Add --json for machine-readable output, --overwrite to replace existing files, --dry-run to see what would be transferred, -v/-vv/-q to adjust logging, and --version to print the version and exit.

JSON output

--json replaces the text output with a single JSON document. The download commands report every file they considered:

{
  "outcomes": [
    {
      "filename": "KboOpenData_0421_2026_08_03_Full.zip",
      "status": "written",
      "location": "./downloads/KboOpenData_0421_2026_08_03_Full.zip",
      "size": 431495168
    }
  ],
  "written": 1,
  "skipped": 0,
  "missing": 0,
  "planned": 0,
  "total_bytes": 431495168,
  "unresolved": ["index 0423"]
}

status is written, skipped, missing or planned, and location and size are null where they do not apply. unresolved names the requests that matched nothing on the server; only download-indexes and download-dates fill it, and it holds labels such as index 0423 or 2026-08-05 rather than filenames. Unlike the text output, --json always reports every file, including the ones sync would otherwise only count.

The other commands are shaped as follows.

Command Payload
show-latest, check-index, check-date {"index", "date", "update", "full"}, or null when nothing matched
list-files An array of those same objects
list-gaps {"missing_indices": [423, 427]}
check-credentials {"files", "pairs", "latest_index"}

In those payloads date is an ISO date, while update and full are bare filenames, either of which is null when the server holds only one of the two.

Exit codes

Code Meaning
0 Success
1 Nothing matched the request, or a batch matched only in part: one or more requested files or values were absent from the server
2 Usage error
3 Configuration, authentication or connection failure, or any other client error
4 The destination could not be written to

Host keys

The client verifies the server's host key against ~/.ssh/known_hosts and refuses to connect to an unknown host. Add the server once with ssh-keyscan, or pass accept_unknown_host_key=True (--accept-unknown-host-key on the CLI) to trust it on first use.

Logging

The package logs through the standard logging module under the kbo_opendata logger and installs no handlers of its own. The CLI configures a stderr handler for its own process.

Development

uv sync --group dev
uv run ruff check . && uv run ruff format --check .
uv run mypy
uv run coverage run -m pytest && uv run coverage report

The test suite is fully offline: the SFTP transport and the download destinations sit behind typed protocols, and the tests drive real in-memory implementations of them rather than mocks.

Reusing the data

The data is covered by the KBO Open Data licence, which you accept at registration — separate from this package's MIT licence, which covers only the code. One restriction is worth stating plainly: personal data from these files may not be reused for direct marketing purposes. See the licence and the CBE privacy statement.

Licence

MIT

Metadata

Release files for otb-kbo-opendata-pyclient 2.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 otb-kbo-opendata-pyclient 2.0.0
File Size Uploaded
otb_kbo_opendata_pyclient-2.0.0.tar.gz 27.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for otb-kbo-opendata-pyclient 2.0.0
File Interpreter ABI Platform
otb_kbo_opendata_pyclient-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 60.9 kB

Release files / otb_kbo_opendata_pyclient-2.0.0.tar.gz

Download URL otb_kbo_opendata_pyclient-2.0.0.tar.gz
Size 27.9 kB
Tags Source
SHA-256 checksum
How to use checksums
fc114843639823e3553a37aba18b6d431d7ea10a32f1fe08fd11f7fa297256e8
BLAKE2b-256 checksum
How to use checksums
7beb2e25567f1a975434066f149ce056133af5c19153fc035b1ddf06d944e334
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 23, 2026.

Transparency log

Release files / otb_kbo_opendata_pyclient-2.0.0-py3-none-any.whl

Download URL otb_kbo_opendata_pyclient-2.0.0-py3-none-any.whl
Size 33.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
348a6d1da7c08b3d47722b200a5e0f5b22887ad115d9e5aba0b9eaf3b3544a2a
BLAKE2b-256 checksum
How to use checksums
ad73238baf38546222969b03633ab2e8d408f37f8a4fcba6d72cc222e38f78a1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

1.0.0

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