Elixir library for Norwegian small business filings — Maskinporten authentication, Altinn 3 API client, BRG XML/iXBRL generation, tax calculation, and shareholder register XML generation.
Ported from the Python CLI tool Wenche.
| Module | Description | Origin |
|---|---|---|
Wenche.Maskinporten |
JWT-based auth against Maskinporten + Altinn token exchange | wenche/auth.py |
Wenche.Systembruker |
System user registration and management for Altinn 3 | wenche/systembruker.py |
Wenche.AltinnClient |
Altinn 3 API client (instances, data upload, completion) | wenche/altinn_client.py |
Wenche.Aarsregnskap |
Annual accounts submission flow (config, validation, submission) | wenche/aarsregnskap.py |
Wenche.BrgXml |
BRG annual statement XML (hovedskjema/underskjema) | wenche/brg_xml.py |
Wenche.Ixbrl |
Inline XBRL (iXBRL) HTML document generation | wenche/xbrl.py |
Wenche.Noter |
Notes (noter) for small enterprises — structured XML + iXBRL text | wenche/noter.py |
Wenche.Skattemelding |
Tax calculation, structured beregn/2, and Altinn 3 submission orchestration for skattemelding (RF-1028/RF-1167) |
wenche/skattemelding.py |
Wenche.SkattemeldingXml |
Skattemelding XML (skattemeldingUpersonlig v5, naeringsspesifikasjon v6, request envelope v2) against Skatteetaten XSDs |
— |
Wenche.SkdSkattemeldingClient |
Skatteetaten REST API client for skattemelding (pre-filled draft, valider) | — |
Wenche.MvaMelding |
VAT return (MVA-melding) submission via Altinn 3. Experimental. | — |
Wenche.MvaMeldingXml |
MVA-melding XML generation (mvaMeldingInnsending + mvaMeldingDto) |
— |
Wenche.Aksjonaerregister |
RF-1086 shareholder register XML generation | wenche/aksjonaerregister.py |
Wenche.SkdClient |
Skatteetaten REST API client for RF-1086 (1086H / 1086U / bekreft) | — |
Wenche.Models |
Data structures (Selskap, Aarsregnskap, Resultatregnskap, Balanse, SkattemeldingKonfig, etc.) | wenche/models.py |
Add wenche to your list of dependencies in mix.exs:
def deps do
[
{:wenche, "~> 0.3.0"}
]
endconfig = [
client_id: "your-client-id",
kid: "your-key-id",
private_key_pem: File.read!("maskinporten_privat.pem"),
env: "prod"
]
# Standard token for instance operations
{:ok, token} = Wenche.Maskinporten.get_altinn_token(config)
# System user token for organization-specific operations
{:ok, token} = Wenche.Maskinporten.get_systemuser_token(config, "912345678")
# Admin token for system register operations
{:ok, admin_token} = Wenche.Maskinporten.get_admin_token(config)alias Wenche.Models.{
Aarsregnskap, Selskap, Resultatregnskap, Balanse,
Driftsinntekter, Driftskostnader, Finansposter,
Eiendeler, Anleggsmidler, Omloepmidler,
EgenkapitalOgGjeld, Egenkapital, LangsiktigGjeld, KortsiktigGjeld
}
# Build company data
selskap = %Selskap{
navn: "Mitt Selskap AS",
org_nummer: "912345678",
daglig_leder: "Ola Nordmann",
styreleder: "Kari Nordmann",
forretningsadresse: "Storgata 1, 0001 Oslo",
stiftelsesaar: 2020,
aksjekapital: 30000
}
# Build financial data
resultatregnskap = %Resultatregnskap{
driftsinntekter: %Driftsinntekter{
salgsinntekter: 0,
andre_driftsinntekter: 0
},
driftskostnader: %Driftskostnader{
loennskostnader: 0,
avskrivninger: 0,
andre_driftskostnader: 5000
},
finansposter: %Finansposter{
utbytte_fra_datterselskap: 100000,
andre_finansinntekter: 500,
rentekostnader: 0,
andre_finanskostnader: 0
}
}
balanse = %Balanse{
eiendeler: %Eiendeler{
anleggsmidler: %Anleggsmidler{
aksjer_i_datterselskap: 500000,
andre_aksjer: 0,
langsiktige_fordringer: 0
},
omloepmidler: %Omloepmidler{
kortsiktige_fordringer: 0,
bankinnskudd: 125500
}
},
egenkapital_og_gjeld: %EgenkapitalOgGjeld{
egenkapital: %Egenkapital{
aksjekapital: 30000,
overkursfond: 0,
annen_egenkapital: 595500
},
langsiktig_gjeld: %LangsiktigGjeld{
laan_fra_aksjonaer: 0,
andre_langsiktige_laan: 0
},
kortsiktig_gjeld: %KortsiktigGjeld{
leverandoergjeld: 0,
skyldige_offentlige_avgifter: 0,
annen_kortsiktig_gjeld: 0
}
}
}
regnskap = %Aarsregnskap{
selskap: selskap,
regnskapsaar: 2025,
resultatregnskap: resultatregnskap,
balanse: balanse
}
# Generate XML documents
hovedskjema = Wenche.BrgXml.generer_hovedskjema(regnskap)
underskjema = Wenche.BrgXml.generer_underskjema(regnskap)
ixbrl_html = Wenche.Ixbrl.generer_ixbrl(regnskap)
# Or submit directly via Altinn
client = Wenche.AltinnClient.new(altinn_token, env: "prod")
{:ok, inbox_url} = Wenche.Aarsregnskap.send_inn(regnskap, client)Wenche.Skattemelding.beregn/2 returns the computed tax return as a structured
map (RF-1167 næringsoppgave, RF-1028 skattemelding, balance overview, equity
note, sammenligning, warnings). Render it however you like.
alias Wenche.Models.SkattemeldingKonfig
konfig = %SkattemeldingKonfig{
anvend_fritaksmetoden: true,
eierandel_datterselskap: 100,
underskudd_til_fremfoering: 0
}
beregning = Wenche.Skattemelding.beregn(regnskap, konfig)
# %{
# selskap: %{navn: ..., org_nummer: ...},
# regnskapsaar: 2025,
# rf_1167: %{driftsinntekter: %{...}, driftskostnader: %{...}, ...},
# rf_1028: %{utbytte: ..., beregnet_skatt: ..., ...},
# balanse: %{i_balanse: true, differanse: 0, ...},
# sammenligning: %{...} | nil,
# egenkapitalnote: %{...} | nil,
# advarsler: [...]
# }alias Wenche.Models.{Aksjonaerregisteroppgave, Aksjonaer}
aksjonaerer = [
%Aksjonaer{
navn: "Ola Nordmann",
fodselsnummer: "12345678901",
antall_aksjer: 100,
aksjeklasse: "A",
utbytte_utbetalt: 50000,
innbetalt_kapital_per_aksje: 300
}
]
oppgave = %Aksjonaerregisteroppgave{
selskap: selskap,
regnskapsaar: 2025,
aksjonaerer: aksjonaerer
}
:ok = Wenche.Aksjonaerregister.valider(oppgave)
xml = Wenche.Aksjonaerregister.generer_xml(oppgave)By default, the system user requests rights for årsregnskap and aksjonærregisteroppgaven.
The skattemelding scope is not included by default because systemic submission requires
being a registered revisor or regnskapsfører. Enable it explicitly via the :features option
if you have the appropriate authorization.
# Get admin token
{:ok, admin_token} = Wenche.Maskinporten.get_admin_token(config)
# Register system with default rights (årsregnskap + aksjonærregister)
{:ok, _} = Wenche.Systembruker.registrer_system(admin_token, vendor_orgnr, client_id,
name: "my_system", description: %{"nb" => "Mitt system", "nn" => "Mitt system", "en" => "My system"})
# Or include skattemelding scope (requires revisor/regnskapsfører authorization)
{:ok, _} = Wenche.Systembruker.registrer_system(admin_token, vendor_orgnr, client_id,
name: "my_system", description: %{"nb" => "...", "nn" => "...", "en" => "..."},
features: [:skattemelding])
# Create system user request for an organization
{:ok, request} = Wenche.Systembruker.opprett_forespoersel(admin_token, vendor_orgnr, org_nummer,
name: "my_system")
# Check approval status
{:ok, status} = Wenche.Systembruker.hent_forespoersel_status(admin_token, request["id"])
# List all approved system users
{:ok, users} = Wenche.Systembruker.hent_systembrukere(admin_token, vendor_orgnr,
name: "my_system")MIT — see LICENSE.
This project is an Elixir port of the Python tool Wenche, originally licensed under the MIT License.