Ekwo OS — Open Source Accounting: Ledger, Invoices And VAT On Your Own Supabase Or Postgres
The core — schema, posting rules, taxes, reports, financial statements,. A country pack for every country listed below, each a versioned set of. ekwo, the command line — one command installs everything on your own
The core — schema, posting rules, taxes, reports, financial statements,. A country pack for every country listed below, each a versioned set of. ekwo, the command line — one command installs everything on your own
02A country pack for every country listed below, each a versioned set of
03ekwo, the command line — one command installs everything on your own
04The MCP server, hosted at https://mcp.ekwo.ai/mcp or run locally with
05Modules for fixed assets, budgets, corporate income tax and electronic
06Format libraries under MIT — the files administrations, banks and
Ekwo OS
Ekwo is open source accounting, ready to use on Ekwo Cloud: the web application for your invoices, your bank statements, your VAT returns and the electronic invoices your country uses, in your own instance, with an account opened in a minute. Ekwo OS is the free and open source core it runs on, under AGPL-3.0: install it on your own Supabase project and own your accounting data, forever.
Built and maintained by Ekwo. Ekwo OS is a schema, the rules of each country as data, and the tools — a command line and an MCP server — with which you, your accountant and the AI agent you choose work in the same books, each with their own access. Ekwo reads any supplier invoice, in any language, with the AI model you choose, and every entry is posted on your confirmation. What Ekwo is, and its limits: DISCLAIMER.md.
From an empty Supabase project to a prepared VAT return, for two countries
side by side. How it was recorded: docs/demo/.
Try it on your own books. Already using Claude? Start with
Claude goes from a free Supabase project to your
own books taken over, questioned and your next VAT return prepared, in about
twenty minutes. Or open the same books in the web application,
Ekwo Cloud.
Why Ekwo
The long version — financial autonomy for every business, accounting as a
commons, a network rather than a vendor — is in .
No FAQ yet. Enrichment fills this from the README on the next crawl.
No tool list yet. Remote servers get one after the first health check.
The data belongs to the business, and the work of keeping the books can be
done by software the business also owns. So the whole accounting core is
open, the data sits in a Postgres database you control, and the interface is
designed for machines as much as for people. Supabase gives the schema a REST
API for free, and an MCP server sits on top of it, so an AI agent can book a
purchase, match a payment or prepare a VAT return on your own data — as you,
under your own row level security, without the data leaving your account.
What ships today:
The core — schema, posting rules, taxes, reports, financial statements,
as migrations you apply to a database you control.
A country pack for every country listed below, each a versioned set of
data with a year of books that proves it.
ekwo, the command line — one command installs everything on your own
Supabase project; the same tool then keeps books from a terminal.
The MCP server, hosted at https://mcp.ekwo.ai/mcp or run locally with
npx @ekwo-ai/mcp, so any AI agent can operate the books.
Modules for fixed assets, budgets, corporate income tax and electronic
invoicing.
Format libraries under MIT — the files administrations, banks and
e-invoicing networks exchange, written and read.
Who it is for: a company that wants to keep its own books with an AI at the
keyboard; an accounting firm that runs several companies inside one
installation; a developer who needs a real double-entry core with tax rules as
data rather than as code; and anyone who wants to leave a proprietary system
with the books intact.
What is in this repository
A double-entry accounting core for Postgres. There is no server to run:
Supabase turns the schema into a REST API with an OpenAPI description, and row
level security decides who sees what.
Double entry, enforced by the database. An entry cannot be posted unless
it balances, and a locked period refuses writes at the trigger — not in a
form validator.
Documents and entries are two layers. An invoice answers to EN 16931 and
Peppol; an entry answers to the chart of accounts. Keeping them apart keeps
both honest.
Country rules are data. A tax points at the ledger accounts it posts to
and at the boxes of the declaration it feeds. Adding a régime is a row, not a
release.
Every label is data, in every language a country pack publishes.
Identifiers stay English; what a person reads comes from the pack, in the
language of the company or of the reader. See
docs/languages.md.
Every country a pack, out of the box. United Arab Emirates (ae), Albania (al), Հայաստան (am), Angola (ao), Argentina (ar), Austria (at), Australia (au), Azərbaycan (az), Bosna i Hercegovina (ba), Bangladesh (bd), Belgium (be), Burkina Faso (bf), България (bg), البحرين (bh), Bénin (bj), Bermuda (bm), Brunei Darussalam (bn), Bolivia (bo), Canada (ca), République démocratique du Congo (cd), Centrafrique (cf), Congo (cg), Schweiz (ch), Côte d’Ivoire (ci), Chile (cl), Cameroun (cm), 中国 (cn), Colombia (co), Costa Rica (cr), Cyprus (cy), Czechia (cz), Germany (de), Danmark (dk), Dominican Republic (do), Algérie (dz), Ecuador (ec), Estonia (ee), مصر (eg), España (es), Ethiopia (et), Finland (fi), France (fr), Gabon (ga), United Kingdom (gb), საქართველო (ge), Guernsey (gg), Ghana (gh), Guinée (gn), Guinée équatoriale (gq), Ελλάδα (gr), Guatemala (gt), Guinée-Bissau (gw), Hong Kong (hk), Honduras (hn), Croatia (hr), Magyarország (hu), Indonesia (id), Ireland (ie), Israel (il), India (in), Ísland (is), Italia (it), Jersey (je), 日本 (jp), Kenya (ke), Comores (km), 대한민국 (kr), Kuwait (kw), Cayman Islands (ky), Қазақстан (kz), Sri Lanka (lk), Lietuva (lt), Luxembourg (lu), Latvija (lv), Maroc (ma), Moldova (md), Crna Gora (me), Северна Македонија (mk), Mali (ml), 澳門 (mo), Malta (mt), Mauritius (mu), México (mx), Malaysia (my), Moçambique (mz), Niger (ne), Nigeria (ng), Nicaragua (ni), Nederland (nl), Norge (no), New Zealand (nz), Oman (om), Panamá (pa), Perú (pe), Philippines (ph), Pakistan (pk), Polska (pl), Portugal (pt), Paraguay (py), Qatar (qa), Romania (ro), Srbija (rs), Rwanda (rw), Saudi Arabia (sa), Sverige (se), Singapore (sg), Slovenia (si), Slovensko (sk), Sénégal (sn), El Salvador (sv), Tchad (td), Togo (tg), ประเทศไทย (th), Tunisie (tn), Türkiye (tr), 臺灣 (tw), Tanzania (tz), Україна (ua), Uganda (ug), United States (us), Uruguay (uy), British Virgin Islands (vg), Việt Nam (vn), Kosova (xk), South Africa (za), Zambia (zm) and Zimbabwe (zw) — each with its chart of accounts, its tax codes, its
declaration boxes and its financial statements, and all of them installed by
ekwo init, so one installation keeps companies in any of these countries.
The version and review status of each are in
docs/packs.md.
Statutory files. The French FEC, XBRL annual accounts, Peppol UBL and
Factur-X invoices, recapitulative statements — written from the ledger by the
format libraries.
Tested on real Postgres. The test suite runs the migrations, the seeds,
the accounting scenarios, the installer and the MCP server against Postgres
compiled to WebAssembly.
Modules
The core lives in the public schema. Beside it, a module is a schema of its
own, with its own migrations, row level security and tests, enabled per
company.
The electronic invoice of a posted sale, in the format the company's country pack declares, checked against the rules of that format, and every sending with its state.
npx -y ekwo-os@latest module list # what is here, and what the database holds
npx -y ekwo-os@latest module enable assets --company "…" # turn one on for a company
Then add the module's schema to the project's exposed schemas (Supabase
dashboard → Project Settings → API); ekwo module enable prints the line.
A module never writes the ledger directly: it hands its lines to the core,
which applies the same balancing, numbering and lock rules as everywhere else.
docs/modules.md is how to write one.
Install on your own Supabase project
Create a project at supabase.com — the free plan is
enough to start — and point the installer at it. Node 20 or later is the only
thing you need locally: no Supabase CLI, no Docker, no clone.
npx -y ekwo-os@latest init
It asks for the connection string, the country, the chart of accounts and the
language where the pack offers a choice, your organisation, the first company
and the address of the first administrator. Then it applies the migrations,
loads every country pack, creates that administrator in your Supabase Auth
and opens the first financial year. Running it twice creates nothing twice.
Ekwo does not create the project and does not pay for it: your books are on
your account from the first row. The installation guide, with every flag and
the four settings to change on your project before going live, is
packages/cli.
Asking an AI agent to do it with you? Point it at AGENTS.md.
Each country also has its own step-by-step page on
ekwo.ai/countries, and the whole documentation is
served to a model as https://ekwo.ai/llms.txt.
Keeping it running
npx -y ekwo-os@latest status # schema version installed against available, instance, companies
npx -y ekwo-os@latest migrate # apply what a new release adds
npx -y ekwo-os@latest doctor # every object this release defines, row level security, privileges
By hand, with the Supabase CLI
The installer is a convenience, not a requirement: the migrations are ordinary
SQL, and supabase db push writes the same history table ekwo migrate does.
git clone https://github.com/Ekwo-ai/ekwo-os.git && cd ekwo-os
supabase link --project-ref <your-project-ref>
supabase db push # applies supabase/migrations in order
Then apply the reference seeds, all of them, in this order (the list is
written from packs/ and is the one supabase/config.toml declares):
Skip supabase/seed/90_demo_company.sql unless you want sample data. Then, as
a signed-in user: init_instance(), claim_instance_admin(), a companies
row with yourself as owner in company_members,
install_country_template() and a first fiscal_years row — the same steps
the installer runs.
The schema in twenty lines
instance one row: who installed it, where, which edition
instance_admins instance administrators
capabilities ── role_capabilities what may be done, and what each preset holds
companies ─┬─ company_members who may read or write, and what they may do
├─ company_invitations an address invited, a token hashed
├─ api_keys machine access, scoped to capabilities
├─ fiscal_years periods, open or closed
├─ accounts chart of accounts, 18 account types
├─ journals ── journal_sequences
├─ contacts customers, suppliers, employees
├─ taxes ── tax_postings ledger account + declaration box, per tax
├─ entries ── entry_lines the ledger; lines carry the truth
├─ products what a line is filled in from, never stock
├─ documents ── document_lines invoices, credit notes, quotes
├─ payments money in and out
├─ reconciliations bilateral matching, by amount
├─ bank_accounts ── bank_statements ── bank_transactions
├─ analytic_axes ── analytic_values ── entry_line_analytics
└─ attachments files, polymorphic
user_preferences one row per person, null everywhere
post_document() turns a document into an entry. trial_balance,
general_ledger, aged_balance, vat_return, ec_sales_list,
financial_statement and fec_lines read the ledger back.
opening_balance() takes over the balances of whatever kept the books before,
and close_fiscal_year() closes a year the way the country pack says.
docs/schema.md describes every table and column, and
docs/mapping.md lines them up against EN 16931.
Who may do what
One installation belongs to one customer, so there is no tenant_id.
instance_admins says who may create companies and invite people, and
company_members gives each person a preset on each company:
Preset
Holds
viewer
every .read — the books, the documents, the chart, the catalogue
client
what a viewer holds, plus handing a file over to whoever keeps the books
accountant
that, plus writing and posting, matching, the settings and the year-end close
owner
that, plus the company itself and its members
What the policies actually test is a capability — documents.post,
payments.write, year_end.close and the rest of
select * from capabilities. A member can be granted or refused one
capability without inventing a role. A firm keeps the books of many companies
in one installation, and the person who runs one of them is a client of that
one and sees no other (docs/firms.md). People are invited
with invite_member(); your users live in your own Supabase Auth, and Ekwo
never holds an account.
Keys for machines. A script — a nightly import, a till, a bank feed — gets
a key from create_api_key(): scoped to one company and a list of
capabilities, never more than the person who issued it holds, stored as a
hash, and withdrawn with revoke_api_key(). It reaches the REST API through
the X-Ekwo-Api-Key header (docs/machine-access.md).
Never hand a script the service_role key.
The TypeScript packages
ekwo-os — the ekwo command line: installing, operating,
and keeping books as a signed-in person. Every command takes --json.
@ekwo-ai/mcp — the MCP server. It acts as the user,
never as service_role, and every write goes through the schema's own
functions. Hosted at https://mcp.ekwo.ai/mcp for clients that add remote
servers (sign-in in the browser, nothing stored in a configuration file), or
npx -y @ekwo-ai/mcp@latest over stdio.
@ekwo-ai/core — the types of the schema, a typed client,
and the book-keeping functions the command line and the MCP server share.
import { EkwoClient } from '@ekwo-ai/core';
import { createClient } from '@supabase/supabase-js';
const ekwo = new EkwoClient(createClient(url, key));
await ekwo.postDocument(documentId);
const balance = await ekwo.trialBalance({ companyId, from: '2026-01-01', to: '2026-12-31' });
const boxes = await ekwo.vatReturn({ companyId, from: '2026-07-01', to: '2026-09-30' });
Format libraries
packages/formats/ holds one MIT package per file
format, organised by format and never by country. None imports the core: the
core produces rows, and a library turns rows into a file, or a file into rows.
Written from the ledger: the French FEC (fec),
XBRL annual accounts (xbrl-cbso), a
periodic VAT return (vat-consignment),
and four recapitulative statements of European Union supplies
(intra-consignment,
des, ecdf,
vd).
Electronic invoices, written and read:peppol-ubl (Peppol BIS Billing 3.0) and
factur-x (Factur-X and ZUGFeRD). Both also
read a received invoice into the same shape, ready to become a purchase draft.
Transmission to administrations, on the business's instruction
The test: does it keep working on its own, with us or without us? If yes, it
belongs here, under AGPL, and no licence key gates it. If no, it is a service,
because something has to be kept alive for it — a contract, a certificate, a
credential, a machine. docs/cloud-services.md lists
them, and says what will never be sold.
Security
Row level security is the whole model: every table carries it, every policy
asks one question — has_capability() — and the reports run as the caller. An
anonymous request sees nothing. The MCP server and the command line refuse a
service_role key for keeping books, and the CI fails if a table ever arrives
without a policy.
Three things the schema cannot do for you, printed by ekwo init at the end
of every installation: turn off public sign-ups on your Supabase project,
keep two administrators, and keep the service_role key off every
machine that does not need it. See
packages/cli
and SECURITY.md.
What this is not
Ekwo is open source data infrastructure. It is not an accounting firm and
gives no accounting, tax, financial, legal or investment advice. Your books,
returns and filings are yours; a country pack is a reading of the rules at a
date, and a review is a professional's good-faith reading, not a guarantee.
Where accounting and tax are regulated professions, consult a professional
authorised in your country. DISCLAIMER.md says this in full,
and is published at ekwo.ai/disclaimer. Read it
before you file anything.
npm install
npm run typecheck
npm test # applies every migration and seed to an in-memory Postgres
npm run build
Tests use PGlite: no Docker and no local Postgres. A
development checkout needs Node 22 or later.
Contributing
Issues and pull requests are welcome. Contributions require the
Contributor Licence Agreement; see CONTRIBUTING.md
for how to work on the schema without breaking a database somebody already
installed.