accounted
← Blog

Embedded Accounting for SaaS

What it actually takes to put Swedish bookkeeping inside your SaaS product today: company provisioning in one POST, scoped API keys, 280+ REST endpoints, OAuth 2.1 for agent access, and an AGPL core you can self-host. Also the honest part: you build the UI, and your customer still connects bank and Skatteverket with their own BankID.

For developersMay 12, 2026Last updated: September 27, 20267 min read

TL;DRWhat it actually takes to put Swedish bookkeeping inside your SaaS product today: company provisioning in one POST, scoped API keys, 280+ REST endpoints, OAuth 2.1 for agent access, and an AGPL core you can self-host. Also the honest part: you build the UI, and your customer still connects bank and Skatteverket with their own BankID.

If your SaaS serves Swedish small businesses or freelancers, accounting is the highest-stickiness feature you can add. Customers don't churn out of the system holding their verifikat.

This article is the developer-side view of what that integration looks like against the product as it exists today — including the part that isn't built yet.

What exists today

Four things you can build on right now:

  1. A REST API with 280+ documented endpoints, OpenAPI 3.1, at https://app.accounted.se/api/v1. Every endpoint returns structured errors, with a recovery hint where there's a known fix.
  2. Company provisioning. POST /api/v1/companies creates a company, set up for bookkeeping, in one call.
  3. Scoped API keys. Keys are minted at /settings/api, act as the user who minted them, and carry explicit capability scopes. Writes take an Idempotency-Key header, and most support dry-run.
  4. An MCP server with 150+ tools over OAuth 2.1, if you want your customers' books reachable by an agent rather than only by your backend.

The core is AGPL-3.0 on GitHub, so you can also read exactly what you're integrating against, or self-host it.

What does not exist yet

Being direct about this, because it's the thing that decides whether embedded accounting is viable for you this quarter:

Provisioning is an API call now. POST /api/v1/companies creates the company (scope companies:write), owned by the key's user or attached to one of their teams. It isn't idempotent: list companies before you retry after a network error.

What the API can't do is the part that is legally attached to a person rather than to your platform: BankID identification, the Skatteverket connection and the bank consent. Inviting a user to a company also happens in the app. So the current shape is:

  • Your platform creates the company over the API.
  • Your customer is invited in accounted and connects bank and Skatteverket once, with their own BankID.
  • Your product then operates their books through your key, or the customer authorises your platform over OAuth 2.1.

That's still a seam in your onboarding, just a smaller one than before. It's fine for platforms with a few hundred high-value customers and something to design around for a self-serve product with thousands of signups.

The integration shape

1. Create a company, or discover the ones a key can reach

curl -X POST https://app.accounted.se/api/v1/companies \
  -H "Authorization: Bearer gnubok_sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Acme AB","entity_type":"aktiebolag","org_number":"5566778899","vat_registered":true,"moms_period":"quarterly","f_skatt":true}'

curl https://app.accounted.se/api/v1/companies \
  -H "Authorization: Bearer gnubok_sk_live_..."

The GET returns the companies the key's user is a member of, with org number, company form and the user's role in each. With a live key the POST creates a real company, and bookkeeping duty starts with it; a test key returns a dry-run preview instead.

2. Operate on one company's books

Every resource is nested under a company ID. Nothing is ambiguous about which ledger you're writing to:

curl https://app.accounted.se/api/v1/companies/{companyId}/transactions \
  -H "Authorization: Bearer gnubok_sk_live_..."

Reports, invoices, suppliers, journal entries, employees, and SIE export all follow the same /api/v1/companies/{companyId}/... shape.

3. React to changes without polling

28 webhook event types are available, among them invoice.paid, journal_entry.committed, period.locked and transaction.categorized. Subscribe with a key holding webhooks:manage, list deliveries per subscription at /api/v1/companies/{companyId}/webhooks/{id}/deliveries, and retry one with POST /api/v1/webhook-deliveries/{id}/retry. Payloads are signed with HMAC-SHA256 in the X-Gnubok-Signature header.

If you'd rather poll, the events:read scope exposes the same event log directly.

Scopes are the actual security model

There's no partner-level credential. A key acts as the user who minted it: it reaches the companies that user is a member of (the URL picks which) and nothing else, with only the scopes it carries. If your platform's user owns every company it provisions, one key reaches all of them, so guard it accordingly. The scopes you'll reach for most:

ScopeWhat it unlocks
transactions:readList transactions, category suggestions
transactions:writeCategorise, match against invoices, receipt matching
invoices:read/writeCustomer ledger, create, send, mark paid, credit
reports:readTrial balance, general ledger, VAT, KPI, SIE export
events:readPoll the event log as a webhook fallback
webhooks:manageCreate and manage webhook subscriptions
companies:writeCreate companies, update company settings

Keys with no explicit scopes fall back to read-only. Over OAuth, a client registered under Settings (which is what your platform would be) gets the scopes it requests pre-ticked, read scopes only if it requests none, and the user can untick any of them.

One deliberate constraint worth knowing before you design around it: a key that can both stage a write and approve it is blocked by default. Staging scopes and pending_operations:approve on the same credential are refused unless the user explicitly acknowledges the combination, and the acknowledgement is recorded on the key, because it lets an automated agent post to the books with nobody else reviewing. A key can also carry an unattended commit limit in SEK, above which a human approves.

Building the UI

You build it. There is no embeddable React component library — that's a thing I've seen people assume, so: it doesn't exist.

What you get is the API, the OpenAPI spec, and llms.txt / llms-full.txt if you want to point a coding agent at the surface. What you build on top — the categorisation flow, the reports view, the invoice form — is yours, in your design system, on your domain.

For a platform with a competent frontend team, a usable accounting surface covering categorisation, invoices, and the three core reports is a multi-week project, not a multi-quarter one. The compliance-heavy parts — VAT rules, period locking, SIE, BAS validation — are the parts you're not building.

Compliance: who owns what

The books belong to your customer

Your end customer is the obligated party under Bokföringslagen and the data controller under GDPR. Your platform is a processor; accounted is a sub-processor. Standard chain, standard DPA.

Practically: they can export complete SIE4 at any time, and you cannot delete their books unilaterally. Design your offboarding assuming they'll leave with everything.

The audit log is per company

Every state change is an event attributed to a user and a session. A revisor auditing your customer's books reads it directly. Agent activity is traceable to the credential that produced it.

Filings stay between your customer and Skatteverket

Each company connects its Skatteverket account with a person's BankID, or files manually. Your product can surface "file your VAT return" as a feature, but the filing is signed with BankID by a human. You can't file on their behalf from your backend.

Three questions before you commit

  1. How Swedish is your customer base? This works for Swedish-registered companies. If most of your customers are international, this is the wrong integration — the whole value is doing one jurisdiction properly.

  2. Can you live with the onboarding seam? Provisioning is an API call now, but each customer still connects bank and Skatteverket once, with their own BankID. If that step kills your activation funnel, plan around it before you build.

  3. Do you have a monetisation story? Embedded accounting given away free tends to be treated as a low-value feature. Customers value it partly because it costs money to operate.

What to do next

  1. Read The Accounting API for the AI Era for the broader positioning.
  2. Read Building on an Open Source Swedish Ledger for self-hosting and the validation core.
  3. Mint a gnubok_sk_test_* key and call /api/v1/companies before you plan anything. Test keys simulate every write (forced dry-run) but read real data: there is no separate sandbox.
  4. Contact us via /byraer for platform terms. Embedded pricing is set after a review of your volume and use case, and there's no published rate card.

Embedded accounting is underrated as a growth lever for Swedish vertical SaaS. It's also further from turnkey than the category's marketing usually admits. Both things are true, and you should plan against the second one.

Frequently asked

Why would a SaaS want to embed accounting?
Stickiness and revenue. Customers don't leave when their books live inside your product, and a per-customer accounting subscription stacks on top of your existing price. For verticalised SaaS in e-commerce, freelance platforms, or agency tooling, it's often the obvious upsell.
Is this Plaid for accounting?
Closer to a ledger you rent than a data aggregator. Plaid is read-only account aggregation; accounted is a full Swedish double-entry ledger with validation, VAT rules, and Skatteverket filings. You're not reading financial data, you're producing it.
Can I provision a new customer's books via API?
Yes. `POST /api/v1/companies` (scope `companies:write`) creates the company with its BAS chart, first fiscal period and tax deadlines in one call, owned by the key's user or attached to one of their teams. It isn't idempotent, so list companies before retrying after a network error. Inviting your customer as a user happens in the app.
How is one customer's data isolated from another's?
Every company is a separate ledger with its own accounts, fiscal periods, and audit log. An API key acts as the user who minted it: it reaches only the companies that user is a member of, and only with the capability scopes it carries (`transactions:read`, `invoices:write`, and so on). A viewer membership is refused for every write.
What's the commercial model?
There's no published embedded price list. Hosted companies are priced per company (see /priser), and a platform arrangement is set after a review of your volume and use case. Start at /byraer.
Next

Start building.

REST + MCP. Open source under AGPL. SIE4 in, SIE4 out. Self-host or use the managed version.