Building on an Open Source Swedish Ledger
gnubok is AGPL-licensed, Swedish-double-entry, and exposes ~70 MCP tools plus a complete REST surface. This is a developer-side tour: how to clone and self-host, how to issue tokens with scopes, how the deterministic validation layer works, and what you actually get for free that you'd otherwise build.
TL;DRgnubok is AGPL-licensed, Swedish-double-entry, and exposes ~70 MCP tools plus a complete REST surface. This is a developer-side tour: how to clone and self-host, how to issue tokens with scopes, how the deterministic validation layer works, and what you actually get for free that you'd otherwise build.
This article is the developer-facing entry point if you want to actually build with gnubok rather than evaluate it from the outside. It covers self-hosting, the validation core, how MCP and REST relate, and what tools are available out of the box. It assumes you've read The Accounting API for the AI Era at least once.
Architecture in one diagram
┌──────────────────────────────────────────────────────┐
│ Clients (Web UI / Claude Desktop / Cursor / SDK) │
└──────────────────────────────────────────────────────┘
│ │
│ REST │ MCP
▼ ▼
┌──────────────────────────────────────────────────────┐
│ API layer (Fastify + tRPC) │
│ - Token scopes, rate limiting, idempotency cache │
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Deterministic validation core (Rust) │
│ - Debit/credit balance │
│ - Account plan + BAS validation │
│ - Period state machine │
│ - Swedish VAT rules │
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Append-only event log (Postgres / SQLite) │
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Derived projections: accounts, balances, reports │
└──────────────────────────────────────────────────────┘
Three layers, two read paths, one write path. Everything that writes goes through validation. Everything that reads comes from projections derived from the event log.
Self-hosting in 10 minutes
Prerequisites: Node 24, Postgres 16 (or use the SQLite path for dev), Docker optional.
git clone https://github.com/erp-mafia/gnubok.git
cd gnubok
cp .env.example .env
# Set DATABASE_URL or leave the SQLite default
npm install
npm run db:migrate
npm run dev
That's the full setup. You now have a gnubok instance at http://localhost:3000 with the admin UI, the REST API at /api, and a local MCP socket that you can point Claude Desktop at.
For production deployment we publish a Docker image at ghcr.io/erp-mafia/gnubok and a Helm chart for Kubernetes. The recommended deployment for single-tenant Pro users is a managed instance from us; for agencies and SaaS embedders, self-hosting is supported with commercial licensing.
Issuing tokens
Tokens have explicit scopes. Defining tight scopes is the difference between safe agentic access and a security incident.
Scopes you'll use most:
read— all read endpoints; safe to issue widely for dashboards and analytics.transactions:write— create transactions, but not delete or modify locked periods.invoices:write— create and send invoices; useful for billing automation.agent— bundle scope for MCP server use; includes most write scopes but every write produces apending_operationrequiring human approval.admin— full access; reserve for root operators only.
Issuing via the API:
curl -X POST https://app.gnubok.se/api/tokens \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{
"name": "stripe-webhook-prod",
"scopes": ["transactions:write"],
"expires_at": "2027-01-01T00:00:00Z"
}'
Token strings are returned exactly once. Rotation is supported via the same endpoint with replaces: "tok_...".
What the validation core does
Every write hits the validation core before anything is committed. Validators (Rust, deterministic, fast):
- Balance check: total debits equal total credits within rounding tolerance.
- Account existence: every line's account exists in the chart of accounts.
- Period state: target period is open. Locked periods reject writes.
- VAT consistency: VAT codes match account types; reverse-charge cases flagged.
- Idempotency: if
idempotency_keymatches a previous successful operation, return the original result without creating a duplicate.
A failure looks like:
{
"error": "validation_failed",
"code": "balance_mismatch",
"details": {
"debit_total": 1000.00,
"credit_total": 999.99,
"tolerance": 0.005
}
}
Structured. Specific. LLMs and humans both parse it without ambiguity.
The MCP tool surface
The MCP server exposes about 70 tools today, grouped by domain. Discovery happens automatically when a client connects: it calls tools/list and receives schema for every available tool.
Top-level domains:
- Transactions: list, categorize, match against invoices.
- Invoices: customer ledger, create, send, credit.
- Supplier invoices: inbox processing, approval flows.
- Reports: trial balance, income statement, balance sheet, KPI.
- VAT: report generation, period review, close.
- Period management: open, lock, close. Year-end orchestration.
- Payroll: salary run, AGI file generation.
- Audit and migration: SIE4 import/export, audit package generation.
Full inventory on the GitHub README. Discoverability is the point: a developer (or agent) can call gnubok_list_skills to see workflows, then gnubok_load_skill to fetch step-by-step instructions for a specific task.
Pending operations and approval flows
Every write through the MCP server stages a pending_operation with a risk level. The shape:
{
"operation_id": "po_2026_04829",
"type": "categorize_transaction",
"risk_level": "low",
"preview": {
"transaction_id": "t_2026_19182",
"proposed_account": "6212",
"proposed_vat": "25%"
},
"requires_approval": true,
"approval_url": "https://app.gnubok.se/approvals/po_2026_04829"
}
Risk levels:
| Level | Examples | Default approval |
|---|---|---|
low | Categorization, read-only ops, reports | Auto-approve optional |
medium | Invoicing, supplier invoices under 5K SEK | Manual default |
high | Payroll runs, period locking, year-end | Manual, always |
For dev environments you can configure auto-approval of low-risk operations. For production, structured human approval at risk-classed checkpoints is what makes agentic access compliant with Bokföringslagen.
REST and MCP are the same surface
A common question: which one should I use?
Answer: both, for different jobs.
- REST for code you write yourself. Webhooks, scheduled jobs, custom integrations, your own product features that talk to the ledger.
- MCP for LLM-driven workflows. Claude Desktop, Cursor, agents that orchestrate multi-step operations.
Both call the same underlying validation core. Both produce events in the same audit log. There's no second-class citizen.
SDK examples
TypeScript:
import { GnubokClient } from "@gnubok/sdk";
const client = new GnubokClient({ token: process.env.GNUBOK_TOKEN! });
const balance = await client.balances.get({ account: "1930" });
console.log(`Cash on 1930: ${balance.amount} SEK`);
await client.transactions.create({
date: "2026-05-12",
description: "Stripe payout",
lines: [
{ account: "1930", debit: 997.60 },
{ account: "3001", credit: 1247.00 },
{ account: "6570", debit: 249.40 },
],
idempotencyKey: `stripe-${payoutId}`,
});
Python:
from gnubok import GnubokClient
client = GnubokClient(token=os.environ["GNUBOK_TOKEN"])
balance = client.balances.get(account="1930")
print(f"Cash on 1930: {balance.amount} SEK")
client.transactions.create(
date="2026-05-12",
description="Stripe payout",
lines=[
{"account": "1930", "debit": 997.60},
{"account": "3001", "credit": 1247.00},
{"account": "6570", "debit": 249.40},
],
idempotency_key=f"stripe-{payout_id}",
)
Both SDKs are typed against the OpenAPI spec generated from the API server. If a field changes, the SDK changes, and your TypeScript compiler will tell you.
Self-hosting with your own LLM
For agencies, regulated industries, or developers who want full data sovereignty: self-host gnubok and point the MCP server at a local LLM via Ollama or your own Anthropic key.
# .env
LLM_PROVIDER=ollama
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=llama3.3:70b
# or
LLM_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-...
The categorization quality depends on model capability. Claude 4.7 and GPT-5 hit ~95 percent on Swedish supplier invoices. Llama 3.3 70B is around 85 percent. Local models are good enough for some workflows and not enough for others; benchmark on your own data before committing.
What's on the roadmap
Three areas of active work in 2026:
- Streaming for long operations. Year-end currently completes in 30–120 seconds and gives no progress. We're adding MCP streaming so agents can show progress visually.
- Resource exposure of historical slices. Today the MCP surface is all tools. We're adding resources so agents can read selectively without knowing which tool returns what.
- Event subscriptions. External systems should be able to subscribe to ledger events (period closed, invoice paid, anomaly detected) without polling.
All three land in the AGPL core. The roadmap is on GitHub if you want to follow or contribute.
What you do next
If you're evaluating: spin up a sandbox account and try the MCP server in read-only mode with Claude Desktop. 20 minutes, no cost.
If you're integrating: read the API documentation, issue a scoped token, and start with a single endpoint. Build something small that works, then expand.
If you're embedding: contact us via /byraer for white-label and commercial licensing. The embedded tier handles multi-tenant, white-label theming, and partner pricing.
If you're contributing: open an issue or discussion on GitHub. PRs reviewed within a week; non-trivial changes benefit from a design discussion first.
The accounting category needed an API-first contender. We're it. The interesting thing isn't gnubok itself; it's what becomes possible when accounting infrastructure is treated like any other piece of modern backend infrastructure: open, scriptable, and primitives instead of products.
Frequently asked
- Why AGPL instead of MIT?
- AGPL protects the open source commons from being forked, hosted as a SaaS, and resold without contribution. The core ledger stays open. Commercial licenses are available for products that embed gnubok inside proprietary SaaS or agency platforms. The SDKs and MCP server are MIT-licensed because they're glue, not the protected substrate.
- Can I run gnubok with my own database?
- Yes. The default deployment uses Postgres. Connection details are config; you can point gnubok at any Postgres 15 or later. SQLite is supported for development and small single-user installs. The append-only event log is the source of truth; the relational schema is a derived projection.
- How does the validation core work?
- Every write operation is a transaction: it produces an event candidate, runs it through deterministic validators (debit/credit balance, account existence, period state, VAT rules, idempotency check), and only commits if all validators pass. Failure returns a structured error. No LLM is in the validation path. This is what makes agentic write access safe.
- What does the multi-tenant architecture look like for agencies?
- Each tenant is a separate ledger with its own audit log, accounts, fiscal periods. Agencies access their tenants through a partner-scoped API. Token scopes can be limited per tenant. White-label theming is configured at the agency level. Pricing for agencies is on a rev-share or volume basis, not per-tenant subscription.
- How do I contribute?
- Open issues for bugs and feature requests on GitHub. For non-trivial changes, open a discussion first so we can align on direction. PRs welcome but expect review. Tests required for new functionality. We use conventional commits.
Last updated: May 12, 2026