After 3 years building a SaaS accounting system for the Norwegian market,
I open-sourced the VAT filing module as a standalone Java library.
This is what I learned.
The problem
If you've ever tried to integrate with Norwegian government APIs, you know
the documentation is thorough — but the gap between reading the docs and
actually getting it working in production is large.
MVA-melding (Norwegian VAT return) looks simple on the surface: generate
some XML, submit it. In reality it's a 15-step chain involving three
separate systems, each with its own auth model and failure modes.
What the flow actually looks like
Most documentation shows you the happy path. Here's what you're actually
dealing with:
Step 1: ID-porten authentication
ID-porten requires PKCE with private_key_jwt — not a simple OAuth flow.
You generate an RSA key pair, upload the public JWKS to Digdir's
self-service portal, then sign your own JWT client assertion on every
token request. The state parameter must survive a browser redirect, which
means you need server-side session storage (we use Redis).
Step 2: Altinn 3 instance lifecycle
This is where most implementations get stuck. Altinn 3 is not a simple
REST API — it's a stateful workflow engine. The sequence is:
create instance → upload mva-melding XML → upload innsending XML →
complete data → sign → complete → poll feedback
Each step returns an updated instance object. You must persist state
between steps. If you skip a step or call them out of order, you get
cryptic 409 errors with no explanation.
Step 3: MVA sign rules
This is the part nobody documents well. Norwegian accounting uses
internal MVA-koder (1-91) that don't map 1:1 to what Skatteetaten
expects in the XML.
The tricky ones are reverse-charge codes 81, 83, 86, 88, and 91.
These generate two lines in the XML — one for output VAT (positive)
and one for input VAT (negative). If you treat them as a single line,
Skatteetaten's validator rejects the submission with a validation error
that points at the wrong place.
Also: output VAT in your ledger is a credit (positive in accounting
terms), but must appear as positive in the XML too — but input VAT is
a debit, and must appear as negative. The sign convention is not
intuitive if you're coming from standard accounting logic.
What I open-sourced
mva-melding-java handles the complete flow:
- Filing calendar generation with Norwegian public holiday adjustments
- Ledger aggregation with correct MVA sign rules
- Full ID-porten PKCE login flow
- Altinn 3 submission, signing, and feedback polling
- Payment info extraction from
betalingsinformasjon.xml - Ports-and-adapters design — implement one interface to connect your own ledger
I also open-sourced a companion library for EHF 3.0 / PEPPOL BIS
Billing 3.0 invoice transmission (oxalis-spring-boot-starter),
which handles the Guice↔Spring isolation problem that makes
oxalis-ng painful to use in Spring Boot applications.
The one thing I wish someone had told me
The Altinn 3 feedback endpoint is asynchronous. After you complete
the submission, Skatteetaten processes the return in the background —
this can take anywhere from a few seconds to several minutes.
You need a polling loop. Don't block a request thread waiting for it.
We run a scheduler that checks every 5 minutes for instances in
UPLOAD_COMPLETE state and polls for feedback.
Links
- MVA-melding library: https://github.com/guangcode/mva-melding-java
- EHF/PEPPOL starter: https://github.com/guangcode/oxalis-spring-boot-starter
If you're working on Norwegian e-invoicing or tax integrations,
feel free to open an issue or leave a comment here.
Top comments (0)