MVA-melding (VAT return) submission to Skatteetaten via Altinn 3.
Supports submitting and validating MVA-meldinger for Norwegian companies.
Authentication
Skatteetaten's MVA-melding API only supports ID-porten (end-user authentication). Maskinporten and system users are not supported — there is no Wenche helper to obtain a token here, and the systembruker flow cannot grant MVA rights.
Callers must obtain an Altinn platform token themselves by:
- Authenticating the end user via ID-porten
- Exchanging the resulting token for an Altinn token
The same Altinn token is used for both validation and submission. The scope tuples required from ID-porten are:
- Validation:
openid skatteetaten:mvameldingvalidering - Submission:
openid altinn:instances.read altinn:instances.write
(The deprecated skatteetaten:mvameldinginnsending scope is being phased
out during 2026 and replaced by the two Altinn instance scopes above.)
Pass the resulting Altinn token as :token to valider/2 or via the
AltinnClient for send_inn/3.
MVA Data
The mva_data parameter is a map with these keys:
:org_nummer— organization number (string):termin— bi-monthly period 1-6:year— tax year (integer):linjer— list of%{mva_kode: integer, grunnlag: number, sats: number, merverdiavgift: number}:fastsatt_merverdiavgift— total MVA to pay (negative = refund):system_name— name of submitting system (default "Kontira")
Experimental
This module is experimental and untested in production.
Summary
Functions
Submits an MVA-melding to Skatteetaten via Altinn 3.
Validates an MVA-melding against Skatteetaten's validation API.
Functions
Submits an MVA-melding to Skatteetaten via Altinn 3.
Mirrors the Skatteetaten reference flow documented at
https://skatteetaten.github.io/mva-meldingen/english/implementationguide/
(sequence diagram in github.com/Skatteetaten/mva-meldingen,
docs/documentation/api/Mva-Melding-Innsending-Sekvensdiagram.txt):
- Generate konvolutt (envelope) and melding XML
POST /instances— create Altinn instancePUT /data/{guid}(mvaMeldingInnsending) — upload konvoluttPOST /data?dataType=mvamelding— upload meldingPUT /process/next("Fullfør Utfylling") — utfylling → bekreftelsePUT /process/next("Fullfør Innsending") — bekreftelse → tilbakemelding
Both process/next calls return 200; the second one's response is what
Skatteetaten's betalingsinformasjon.xml is generated from. We use
fullfoor_instans/3 for both because Skatteetaten labels both transitions
as "Fullfør" steps in their documentation — neste_prosesssteg/3 would
work identically (same endpoint, same accepted status codes for this app).
To inspect the generated XML without submitting, call
Wenche.MvaMeldingXml.generer_konvolutt_xml/1 and
Wenche.MvaMeldingXml.generer_melding_xml/1 directly.
Returns {:ok, inbox_url} or {:error, reason}.
Validates an MVA-melding against Skatteetaten's validation API.
Options
:env—"test"or"prod"(default:"prod"):token— Altinn/Maskinporten token for authentication:req_options— additional options merged intoReqcalls (e.g. for test stubs)
Skatteetaten's validation endpoint always responds with an XML
<valideringsresultat> document — even when the melding is invalid the
HTTP status is 200 and the payload describes the rule violations. This
function parses that document into:
%{
avvik_ved_meldingslevering: "ok" | "ugyldig skattemelding" | "advarsel" | nil,
avvik: [
%{
sti_til_avvik: String.t() | nil,
mva_kode: String.t() | nil,
begrunnelse: String.t() | nil,
avvikstype: String.t() | nil,
avvik_kode: String.t() | nil,
regel_definisjon: String.t() | nil
}
],
raw_xml: String.t()
}Returns {:ok, validation_result} or {:error, reason}.