Commons Hub Brussels — open data
Commons Hub Brussels (Rue de la Madeleine 51, 1000 Brussels) runs its books in
the open. Every hour the chb CLI (https://github.com/CommonsHub/chb) pulls
the Hub's bank accounts, Stripe, Odoo accounting, on-chain wallets, room
calendars and event calendars, and publishes three versions of the result:
for stewards, for members, and for everyone. This API serves the one for
everyone.
- Base URL:
https://commonshub.brussels/opendata - Read-only,
GETonly, no key, no login. CORS is open (*), so a browser app can call it directly. - JSON unless the file says otherwise (
.md,.csv,.ics, images). - Responses are cached for 5 minutes. The data itself refreshes hourly.
- Be gentle: cache what you download, do not poll more than once every few minutes.
- Licence: Open Database License (ODbL) v1.0 — see Licence below.
- What changed lately: https://commonshub.brussels/opendata/changelog (also
.md,.json, and an Atom feed athttps://commonshub.brussels/opendata/changelog.xml). Check it when something you rely on looks different.
Endpoints
| URL | returns |
|---|---|
https://commonshub.brussels/opendata.md (or https://commonshub.brussels/opendata/SKILL.md) | this skill, as markdown; https://commonshub.brussels/opendata is the same page in HTML |
https://commonshub.brussels/opendata/changelog (HTML), .md, .json, .xml (Atom) | what is new or has changed in the dataset, newest first; breaking changes are marked |
https://commonshub.brussels/opendata/index.json | every year and month that has data, with links |
https://commonshub.brussels/opendata/monthly.json | one row per month since the start: money in/out, expenses, invoiced income, bookings, events, door, members, tokens |
https://commonshub.brussels/opendata/{YYYY}/monthly.json | the same, for one year |
https://commonshub.brussels/opendata/annotations.json | known one-off events in the data (test mints, incidents, changes of method) and known bugs at the source |
https://commonshub.brussels/opendata/latest | the files of the current state: newest month and lifetime rollups |
https://commonshub.brussels/opendata/{YYYY} | the files of a whole year (rollups) |
https://commonshub.brussels/opendata/{YYYY}/{MM} | the files of one month (MM is two digits: 2026/09) |
https://commonshub.brussels/opendata/{period}/{file} | one file, e.g. https://commonshub.brussels/opendata/2026/09/expenses.json |
https://commonshub.brussels/opendata/{YYYY}/{MM}/events/images/{file} | an event cover image |
A listing answers { "period": "2026/09", "files": [{ "file", "href", "bytes", "description" }] }.
Every month and every year that has data has every file of its scope, older months included:
a month with nothing to report has the file with empty lists ("expenses": []). A 404 means the
period is not generated yet, or the path is not part of the open dataset.
Files
Scope: M = month (/{YYYY}/{MM}/), Y = year (/{YYYY}/), L = /latest/.
| file | scope | what |
|---|---|---|
transactions.json | M, L | Every transaction: amount, direction, account, category, collective. No counterparty, no bank narration. |
counterparties.json | M, L | The Hub's own accounts. |
summary.json | M, L | Totals per account, collective and category. |
commissions.json | M | The monthly fiscal-host commission each collective pays the Hub. |
inbound_spreads.json | M | Transactions spread over several months: the share landing in this month. No counterparty. |
activitygrid.json | Y, L | Contributors and photos per month (counts only). |
members.json | M, L | Membership summary: counts and totals only, no member list. |
door.json | M, L | Door openings: counts only. |
events.json | M, Y, L | Events as published on the public calendar: name, times, place, host, cover, url. |
events.csv | Y | One year of events, one row each (no attendance or income columns). |
events.md | L | Upcoming events, as markdown. |
rooms.md | L | The rooms and their prices, as markdown. |
calendars/public.ics | M | Room bookings feed (no person names). |
expenses.json | M, Y | Every vendor bill, credit note and expense claim, line by line. Organisations and sole traders named; individuals anonymous. |
vendors.json | M, Y | One row per vendor with totals; individuals merged per category. |
customers.json | M, Y | One row per customer with totals; only organisations named. |
bookings.json | M, Y | Room occupancy (room, start, end), room-rental revenue per room. |
pending-bills.json | L | Bills the Hub still has to pay (the "help us pay" list). |
annual-accounts.json | Y, L | The annual accounts filed with the National Bank: key figures by NBB code, the filed statements (PDF) and consistency checks. |
categories.json | L | The category taxonomy: slug, label, direction (income/expense/both), group and the PCMN accounts that map to it. |
accounts-chart.json | L | The chart of accounts used in Odoo (Belgian PCMN): every account number with its label in English, French and Dutch, class and group; used marks the accounts with entries. |
ledger-balances.json | Y | Per account and year: opening balance, debit, credit and closing balance of posted entries (a trial balance). Accounts of individuals merged, payroll as one line. |
hashes.json | M, L | Integrity manifest: per-provider counts and content hashes. |
vat.json | Y, L | Quarterly VAT declarations filed with the Belgian State. |
Quick start
# What exists?
curl -s https://commonshub.brussels/opendata/index.json | jq '.periods[] | {year, months: [.months[].month]}'
# The Hub's own euro cash flow, month by month (one request)
curl -s https://commonshub.brussels/opendata/monthly.json | jq -r '.months[] | [.month, .money.byCollective.commonshub.in, .money.byCollective.commonshub.out] | @tsv'
# Who did the Hub pay in 2026, and how much? (year rollup, largest first)
curl -s https://commonshub.brussels/opendata/2026/vendors.json | jq -r '.vendors[] | [(.vendor.name // "(\(.individuals) individuals)"), .category, .totalAmount] | @tsv'
# What was bought, line by line, in September 2026?
curl -s https://commonshub.brussels/opendata/2026/09/expenses.json | jq -r '.expenses[] | .vendor.name as $v | .lines[] | [.product // .description, .quantity, .totalAmount, ($v // "individual")] | @tsv'
# Bills still to pay (the "help us pay" list)
curl -s https://commonshub.brussels/opendata/latest/pending-bills.json | jq '.totals, [.bills[] | {number, vendor: .vendor.name, amountDue, dueDate}]'
# Upcoming events
curl -s https://commonshub.brussels/opendata/latest/events.md
import requests
API = "https://commonshub.brussels/opendata"
idx = requests.get(f"{API}/index.json", timeout=30).json()
for y in idx["periods"]:
for m in y["months"]:
r = requests.get(f"{API}/{y['year']}/{m['month']}/summary.json", timeout=30)
if r.ok:
print(y["year"], m["month"], r.json().keys())
const API = "https://commonshub.brussels/opendata";
const { bookings, rooms } = await fetch(`${API}/2026/bookings.json`).then(r => r.json());
Conventions
- Money is in euros unless
currencysays otherwise. Accounting files (expenses,vendors,customers,bookings) carrycurrency: "EUR"and convert foreign documents (totalAmountEUR). Credit notes and refunds subtract, so a month, a category or an income type can have a negative total: a correction, not negative revenue. - Time: dates are
YYYY-MM-DD; timestamps are RFC 3339 with an explicit offset; the Hub's timezone isEurope/Brussels.transactions[].timestampis Unix seconds. All-day events have no clock time. - Every file has
generatedAt. Accounting files also havescope(month|year) andperiod(2026-09|2026). - Ids are stable: a bill keeps the same
id(b-…) inexpenses.jsonandpending-bills.json; an organisation keeps the samevendor.id/customer.id(p-…) across months. Transaction ids are NIP-73 URIs (stripe:txn_…,ethereum:42220:tx:0x…,iban:…:tx:…). - Accounting files are per month and per year only, never in
/latest/.pending-bills.jsonis only in/latest/.
The main files
expenses.json (M, Y) — what the Hub spends, line by line
totals (count, untaxedAmount, totalAmount, paidAmount, amountDue), byCategory[], and expenses[]:
id, number (our accounting number), kind (bill | credit_note | expense = someone reimbursed),
status (pending | partially_paid | paid | reversed | submitted), date, dueDate,
vendor ({ id, type, name, vat }, see below), description, category, collective, event,
amounts, hasDocument, and lines[] (description, product, quantity, unitPrice,
untaxedAmount, totalAmount, vatRate, account: { code, class }).
reversed documents are excluded from totals.
vendors.json / customers.json (M, Y) — who the Hub pays, who pays the Hub
One row per vendor (category, documents, totalAmount, paidAmount, amountDue) or per customer
(incomeType: membership | room_rental | tickets_events | sponsorship | donation |
reinvoiced_costs | sales_services | other_income | other; products, invoices, receivedAmount).
Private individuals are merged into one anonymous row per category / income type with a count
(individuals). Only invoiced income is in customers.json; card payments without an invoice (most
event tickets) are in transactions.json.
bookings.json (M, Y) — how the rooms are used
rooms[] per room: bookings, hours, publicBookings, rentalLines, rentalRevenue (untaxed).
bookings[]: room, start, end, hours, public; a title and eventUrl only when the
booking hosts a public event. rentals[]: room-rental invoice lines (date = invoice date, room,
product, amounts, customer named only when it is an organisation). Invoices and bookings are
deliberately not linked one to one. The year file adds months[] for charts.
Occupancy has been recorded this way only recently (see coverage below). For earlier months,
summary.json summary.bookings counts entries in the room calendars: a different measure, do not
join the two into one series.
transactions.json (M, L) — every movement of money
transactions[]: id, provider (stripe, etherscan, kbcbrussels, …), accountSlug, accountName,
currency, amount, normalizedAmount, grossAmount, fee, type (CREDIT | DEBIT, and
MINT | BURN for community tokens), timestamp, event (when a ticket sale belongs to an event),
metadata.category, metadata.collective, metadata.description (only labels we wrote, never the
bank's narration). counterpartyId is present only when it names nobody (a blockchain address, one of
the Hub's own accounts).
A row with metadata.excluded (a reason) is listed for transparency but is not real activity: leave it out of
sums, as summary.json and contributors.json do.
monthly.json — the time series
Computed on request from the month files. months[], oldest first, one row per month: month
(YYYY-MM), status (closed | current | future), money (eur, byCurrency, byCollective, each { in, out, net, transactions }),
activity, expenses and invoicedIncome (the totals of expenses.json / customers.json),
bookings, door, members, tokens. A section is null when the month has no such file.
fields explains each section. money is summed from transactions.json: euro-denominated rows
only (EUR, EURe, EURb), CREDIT/DEBIT/MINT/BURN only (moves between the Hub's own accounts are
left out), Stripe net of fees. Prefer it over summary.json for money (see known bugs below).
Each row has notes[] (the annotations of that month), and the file has coverage: from when each
section holds data. future months exist because room calendars are booked ahead; they hold calendar
entries only.
summary.json (M, L) — aggregates
Per account, collective and category for a month; /latest/summary.json is the lifetime rollup per
collective (firstMonth, lastMonth, collectives[] with per-currency totals and balances).
Read the known bugs below before using its amounts or balances.
events.json (M, Y, L), events.csv (Y), events.md (L)
events[]: id, name, description, startAt, endAt, allDay, location, url, coverImage,
source, tags, metadata.host — events exactly as their organisers published them on the public
calendar. No guest lists, no attendance, no ticket revenue. Many gatherings at the Hub are not on the
public calendar, so a low count does not mean little happened.
pending-bills.json (L) — the "help us pay" list
Every vendor bill still open: totals (count, totalAmount, amountDue), bills[] with id, number,
status, date, dueDate, vendor: { type: "business" | "individual", name, vat }, lines[],
amountDue. To help pay one, people donate to the Hub with the bill number as reference
(https://commonshub.brussels/donate) — never to the vendor directly.
annual-accounts.json (Y, L) — the accounts filed with the National Bank
One entry per fiscal year in fiscalYears[], filed under the year its period ends (the first fiscal
year ran 1 July 2023 – 31 December 2024 and is labelled "2023" under /2024/): period, status,
filedAt, nbb, keyFigures (total assets 20/58, cash 54/58, equity 10/15, turnover 70, gifts and
subsidies 73, remuneration 62, result of the period 9904, …), figures (every NBB code), documents[]
(the abbreviated balance sheet and profit and loss as PDF, at https://commonshub.brussels/opendata/{YYYY}/annual-accounts/{file}, with
sha256), and checks[]: consistency problems found in the statements (unbalanced totals, a balancing
"other appropriations" entry, an opening balance that does not follow the previous closing). Read the
checks before using the figures: the 2024 and 2025 accounts have known issues under review.
Schema: https://github.com/CommonsHub/chb/blob/main/docs/annual-accounts.md
categories.json (L) — what each category means
https://commonshub.brussels/opendata/latest/categories.json lists every category slug used in metadata.category, expenses.json and the
reports: slug, label, direction (income | expense | both), group, and accounts: the PCMN
account prefixes that map to it (the longest prefix wins). internal_transfer and opening_balance are not
income or spending: leave them out of totals. A transaction's category comes from chb's rules, then a trusted
Nostr annotation, then how the bank line is booked in Odoo (metadata.categorySource: "odoo", with the
matched documents' URIs in metadata.documents). Each month's summary.json has coverage: euro amounts in
and out, and uncategorisedShare (0–1).
accounts-chart.json (L) and ledger-balances.json (Y) — the books by account number
https://commonshub.brussels/opendata/latest/accounts-chart.json is the chart of accounts the Hub uses in Odoo: the Belgian PCMN
(minimum standardised chart), about 1,100 accounts, each with code (e.g. 610150), label and
labels (en_GB, fr_BE, nl_BE), class (1–7), group, Odoo type and reconcile.
used: true marks the ~150 accounts that have entries; filter on it. An account named after a person is
labelled "Account of an individual" (individual: true).
https://commonshub.brussels/opendata/{YYYY}/ledger-balances.json is that year's trial balance by account: opening, debit,
credit, closing (debit positive, closing = opening + debit − credit) and totals. Classes 1–5 open
with everything before 1 January; classes 6–7 open at the start of the fiscal year (fiscalStart; 2024
opens on 2023-07-01). totals.opening is the result of earlier years not yet booked to equity (14).
Accounts of individuals are merged per group (merged: N, e.g. "Current accounts of individuals") and
payroll is one line 62, so no individual amount can be read; the totals are exact. Join the two files
on code to get labels in your language. Refreshed hourly from Odoo: the figures move while the
accounts are being corrected (see annual-accounts.json checks).
hashes.json (M, L) and vat.json (Y, L)
hashes.json is the integrity manifest of a month: per data source, counts and a sha256 over the raw
archives, plus hashes of the public and members trees. /latest/hashes.json indexes every month and
carries one top-level hash for the whole dataset. Use it to prove two copies are the same.
vat.json lists every quarterly VAT return filed with the Belgian State (Intervat): every grid of the
official form, control totals (outputVat, inputVat, net), and corrections (filings[]).
gridLabels names the grids.
Contribute back: tag, describe and comment (Nostr)
The data is read-only, but anyone the community trusts can add to it: categorise a transaction, say what a bill was for, split a cost over several months, or start a discussion. These are signed Nostr events on the community relay. Nothing is written to the website or the dataset directly.
What you can point at
Every item has a stable identifier, in the style of NIP-73 (external content ids):
| item | identifier | where to find it | k |
|---|---|---|---|
| a transaction | its id: stripe:txn_…, ethereum:<chain id>:tx:<hash>, iban:<iban>:tx:<hash> | transactions.json | stripe:txn, ethereum:tx, iban:tx |
| a vendor bill, credit note or customer invoice | its Odoo URI odoo:<host>:<db>:account.move:<id> | uri in expenses.json / pending-bills.json, invoices[] in customers.json, uri on bookings.json rentals | odoo:account.move |
| an expense claim (someone reimbursed) | odoo:<host>:<db>:hr.expense:<id> | uri in expenses.json (kind: "expense") | odoo:hr.expense |
| a recurring cost (rent, furniture, …) | chb:expense:<slug>, e.g. chb:expense:rent | the page https://commonshub.brussels/expenses/<slug> (shown on the website; not an accounting document, so chb does not apply it) | chb:expense |
The Odoo URI is the one global identifier of an accounting document: the same in the open data, on Nostr, on the website and in chb.
Tag or describe an item: a kind 1111 snapshot
{
"kind": 1111,
"content": "Crates for the fridge, September delivery",
"tags": [
["i", "odoo:commonshub.odoo.com:commonshub:account.move:45973"],
["k", "odoo:account.move"],
["category", "cold-drinks"],
["collective", "commonshub"],
["event", "open-commons-day-2026"],
["spread", "2026-09", "275.07"],
["spread", "2026-10", "275.06"],
["t", "app:<your app>"]
]
}
contentis the description. Recognised tags:category,collective,event, andspread([month, amount], repeated, to spread a cost or an income over several months). Use the values already found intransactions.json(metadata.category,metadata.collective).["exclude", "<reason>"]marks a transaction as not real activity (a test mint, a duplicate): chb keeps it intransactions.jsonwithmetadata.excludedset to the reason and leaves it out of every total. A newer snapshot without the tag includes it again.- Each event is a full snapshot: the newest one per identifier wins, whoever wrote it. To change one tag, read the current snapshot first and republish it with every tag you want to keep.
- No uppercase
Itag: that is what marks a comment (below), and readers skip such events here.
Read an item's current tags: {"kinds":[1111],"#i":["<identifier>"]}, drop events that have an
uppercase I, keep the newest.
Comment on an item: NIP-22
A top-level NIP-22 comment, kind 1111, with the identifier both as root (uppercase) and as parent (lowercase):
{
"kind": 1111,
"content": "This bill also covers the milk for the coffee corner.",
"tags": [
["I", "odoo:commonshub.odoo.com:commonshub:account.move:45973"], ["K", "odoo:account.move"],
["i", "odoo:commonshub.odoo.com:commonshub:account.move:45973"], ["k", "odoo:account.move"],
["t", "app:<your app>"]
]
}
Read a thread: {"kinds":[1111],"#I":["<identifier>"]}. Replies follow NIP-22 (lowercase e/k
pointing at the parent comment).
Where to publish, and who may
- Relay:
wss://relay.commonshub.brussels. Reading is open. - Writing is limited to community members: the relay accepts an event when its author's key is on
the allow-list, or is attested by an allow-listed key (kind 31926,
ptag). Members' browser keys are attested when they sign in on https://commonshub.brussels. - An agent needs its own key. Generate one, keep the secret safe, and send the npub with a line
about what the agent will do to hello@commonshub.brussels (or a steward on Discord) to be
allow-listed. A rejected event comes back from the relay with
OK falseand a reason. - Which annotations count. Anyone the relay accepts can publish, but chb applies an annotation to
the published data only when its author is trusted: the seeds in chb's settings (the website's key and
chb's own), anyone a seed follows (their kind 3 contact list), and any key the website attests (kind 31926)
with the
memberorstewardrole for the Commons Hub Discord server; one level deep. Members get this by signing in on https://commonshub.brussels. An untrusted annotation stays visible on the relay but changes nothing. To have an agent's annotations applied, ask to be followed. - Sign as yourself, write for people, and do not mass-edit: every event is public, permanent and attributable to your key.
import { finalizeEvent } from "nostr-tools/pure"; // nostr-tools v2
import { SimplePool } from "nostr-tools/pool";
import { Relay } from "nostr-tools/relay";
import { hexToBytes } from "@noble/hashes/utils";
const RELAY = "wss://relay.commonshub.brussels";
const pool = new SimplePool();
const id = "odoo:commonshub.odoo.com:commonshub:account.move:45973"; // `uri` from expenses.json
// Start from the current snapshot so the tags you do not touch are kept.
const [current] = (await pool.querySync([RELAY], { kinds: [1111], "#i": [id] }))
.filter((e) => !e.tags.some((t) => t[0] === "I"))
.sort((a, b) => b.created_at - a.created_at);
const keep = (current?.tags ?? []).filter((t) => !["i", "k", "category"].includes(t[0]));
const event = finalizeEvent({
kind: 1111,
created_at: Math.floor(Date.now() / 1000),
content: current?.content ?? "",
tags: [["i", id], ["k", "odoo:account.move"], ["category", "cold-drinks"], ...keep],
}, hexToBytes(process.env.NOSTR_SECRET_HEX));
pool.close([RELAY]);
// Publish on a single connection: Relay.publish throws with the relay's
// reason ("blocked: not on allowlist") when the event is refused.
const relay = await Relay.connect(RELAY);
try {
console.log("saved:", await relay.publish(event));
} finally {
relay.close();
}
What happens next
- The website shows a tag or a comment on the item's page as soon as the relay has it.
- At its next hourly run, chb applies the newest trusted annotation per identifier:
category,collective,eventandspreadtotransactions.json(the description asmetadata.note),expenses.json,pending-bills.jsonand room rentals. Comments (uppercaseI) are never applied. - The full guide, and a command-line way to annotate (
chb nostr annotate <uri> --category …): https://github.com/CommonsHub/chb/blob/main/docs/annotations.md
Privacy: what you will and will not find
This dataset is designed to be as transparent as possible and to respect the GDPR. The filtering happens before a file is written, so the data simply is not there.
- Organisations are named (companies, associations, public bodies) as vendors and customers, with their VAT number and what they sold or bought. That is how you can follow the money.
- Sole traders (a person registered for VAT) are named as vendors with their VAT number, which is public in the Belgian company register, and the products they sold — but not the free text of their invoices, their invoice reference, or the event a bill is tagged with.
- Private individuals are never named. They appear only by type
(
{"type": "individual"}, sometimes"member": true), merged per category, with no id, so nobody can be followed from month to month. - No invoice links a person to an event. Bills from individuals and sole traders lose their event tag and free text; room-rental invoice lines show the room and the invoice date, never who rented it unless it is an organisation; bookings show a title only for public events.
- Never published: emails, phone numbers, addresses, IBANs and BICs of third parties, Stripe/Odoo ids, payroll details (payroll appears as "Payroll" with an amount), bank narrations, attendee lists, door-opening dates, member lists, Discord profiles and photos.
- Event hosts (
metadata.host) are shown as the organisers published them on the public calendar.
Ground rules for reuse
- Do not try to re-identify anonymous rows, and do not join this data with other sources to attach a name, a date or a place to a private individual.
- If you find personal data that should not be here, stop using it and tell hello@commonshub.brussels: it will be removed at the source.
- Respect the licence (below).
Licence
The dataset is published under the Open Database License (ODbL) v1.0: https://opendatacommons.org/licenses/odbl/1-0/
(license in index.json, and a Link: <…>; rel="license" header on every response).
-
Attribute. Wherever you use or show the data, credit it. For example:
Contains data from Commons Hub Brussels, available under the Open Database License (ODbL): https://commonshub.brussels/opendata
Keep the
generatedAtof the files you used, so others can reproduce your result. -
Share alike. If you publicly use a database derived from this one (data you combined, cleaned, enriched or restructured), you must offer that derived database under the ODbL too, or the changes needed to rebuild it. Public use includes a public website, app or API built on it.
-
Produced works are yours. Charts, articles, slides and apps made from the data may use any licence, as long as they carry the attribution notice above.
-
Keep it open. Do not add technical restrictions (DRM) to copies you distribute without also offering an unrestricted copy.
-
Private analysis owes nothing back. Sharing your derived data with the Hub is always welcome: hello@commonshub.brussels.
The ODbL covers the database. It does not lift the ground rules above: personal-data protection (GDPR) applies to anyone who processes the data, whatever the licence.
Data quality
The data is published as chb produces it. Work around what follows rather than reporting it as a finding.
Coverage
From when each section of monthly.json holds data, over closed months (also coverage in that
file). Before "every month since", a 0 may mean "not recorded" rather than "none": do not chart it as
a drop. Events are sparse by nature (see events.json), so their gaps are real.
| section | first month with data | every month since | empty months since first |
|---|---|---|---|
| money | 2024-01 | 2024-01 | 0 |
| expenses | 2024-01 | 2024-11 | 1 |
| invoicedIncome | 2024-09 | 2024-12 | 2 |
| bookings (bookings.json) | 2024-09 | 2025-01 | 1 |
| room calendar entries (activity.bookings) | 2024-06 | 2024-06 | 0 |
| events (activity.events) | 2024-06 | 2026-05 | 2 |
| contributors (activity.contributors) | 2024-09 | 2024-09 | 0 |
| door | 2026-08 | 2026-08 | 0 |
| members | 2024-08 | 2024-08 | 0 |
| tokens (CHT) | 2024-06 | 2024-06 | 0 |
Annotations
One-off events in the data (incident = a real event that distorts totals: mention it rather than averaging over it; test = not real activity: exclude it; method = how the data is recorded changed: do not compare across it naively). Machine-readable at https://commonshub.brussels/opendata/annotations.json,
and on the matching rows of monthly.json as notes[].
- 2025-01,
tokens.CHT(test): Test mint of 10⁶ CHT, burnt again the same month. Since chb 3.26 the mint and burn are marked metadata.excluded and left out of every total. - 2025-09,
tokens.CHT(test): Test mint of 2×10¹² CHT, burnt again the same month (three burns). Since chb 3.26 they are marked metadata.excluded and left out of every total. - 2026-01,
expenses(method): Expense categories switch from Odoo account classes ("Services and other goods", "Balance sheet") to custom tags ("furniture", "cold-drinks"); both can appear in one month. Compare years with lines[].account.code, not category. - 2026-05,
money(incident): One outflow of about €110k EURe from the account 202605-savings-hacked: a real loss, which dominates any 2026 total.
Known bugs at the source
- summary.json counts transfers between the Hub's own accounts as income and spending. collectives[] includes INTERNAL rows (savings → checking, Stripe payouts), so /latest/summary.json gives commonshub a lifetime EUR "end balance" of hundreds of thousands of euros. It is not a bank balance. Workaround: Use monthly.json money, or sum transactions.json without INTERNAL and TRANSFER rows. Real balances: /finance.
- summary.json accounts[] and currencies[] miss on-chain euros. EURe movements on Gnosis (Monerium mints and burns, which carry rent, catering and many incoming payments) show in: 0, out: 0. Workaround: Use monthly.json money, which counts them.
Good to know
- Odoo is pulled hourly, but a bill stays
pendinguntil it is reconciled with its payment, so utilities paid by direct debit can look unpaid for a while. - A vendor that shows as anonymous but is a company is fixed in Odoo (mark it as a company, add its VAT number); it reappears by name at the next hourly run.
- The schemas and the reasoning behind each field are documented in the chb repository: https://github.com/CommonsHub/chb/blob/main/docs/website.md and https://github.com/CommonsHub/chb/blob/main/docs/accounting-data.md
- Other entry points: https://commonshub.brussels/llms.txt (the site for agents), https://commonshub.brussels/events.md, https://commonshub.brussels/rooms.md, https://commonshub.brussels/finance.md, https://commonshub.brussels/economy.md, https://commonshub.brussels/community.md (month-by-month summaries), https://commonshub.brussels/finance (the human view of the same data).
/data/and/api/on the website are internal and may change or require a login: use/opendata.