Cookbook: compute and review a VAT declaration
Compute the Swedish momsdeklaration rutor 05-62 from your posted general-ledger entries and prepare the numbers for manual submission to Skatteverket.
This is the operational companion to the Reports reference. v1 does NOT submit the declaration to Skatteverket directly: that path exists via the BankID-gated Skatteverket extension, not the public REST API. v1 produces the numbers and the receipt-quality JSON for manual submission via Skatteverket Mina Sidor.
What you'll need
- A test API key with
reports:readscope. - All transactions for the period categorised and posted (see ingest-bank-transactions cookbook).
- The company's
moms_redovisningcycle configured: monthly (kvartalsvis is supported for small companies with omsättning ≤ 1M SEK; the API doesn't dictate cadence, your bookkeeping does).
1. Compute the declaration
GET /reports/vat-declaration returns all the rutor plus supporting counts and a per-source breakdown. The period is selected with three required params — period_type (monthly|quarterly|yearly), year, and period (monthly 1-12, quarterly 1-4, yearly 1) — plus an optional accounting_method (accrual|cash, default accrual):
curl "https://app.gnubok.se/api/v1/companies/$COMPANY_ID/reports/vat-declaration?period_type=monthly&year=2026&period=4" \
-H "Authorization: Bearer gnubok_sk_test_..."
Response (abbreviated):
{
"data": {
"period": { "type": "monthly", "year": 2026, "period": 4, "start": "2026-04-01", "end": "2026-04-30" },
"rutor": {
"ruta05": 133300.00,
"ruta06": 0.00,
"ruta10": 31075.00,
"ruta11": 720.00,
"ruta12": 180.00,
"ruta20": 0.00,
"ruta21": 20800.00,
"ruta22": 0.00,
"ruta23": 0.00,
"ruta24": 0.00,
"ruta30": 5200.00,
"ruta31": 0.00,
"ruta32": 0.00,
"ruta39": 3450.00,
"ruta40": 0.00,
"ruta48": 17547.00,
"ruta50": 0.00,
"ruta60": 0.00,
"ruta61": 0.00,
"ruta62": 0.00,
"ruta49": 19628.00
},
"invoiceCount": 42,
"transactionCount": 15,
"breakdown": {
"invoices": {
"ruta05": 133300.00, "ruta06": 0.00, "ruta07": 0.00,
"ruta10": 31075.00, "ruta11": 720.00, "ruta12": 180.00,
"ruta39": 3450.00, "ruta40": 0.00,
"base25": 124300.00, "base12": 6000.00, "base6": 3000.00
},
"transactions": { "ruta48": 17547.00 },
"receipts": { "ruta48": 0.00 },
"reverseCharge": {
"ruta20": 0.00, "ruta21": 20800.00, "ruta22": 0.00, "ruta23": 0.00, "ruta24": 0.00,
"ruta30": 5200.00, "ruta31": 0.00, "ruta32": 0.00
}
}
},
"meta": { "request_id": "req_...", "api_version": "2026-05-12" }
}
Ruta 49 = (utgående moms 10+11+12 + omvänd skattskyldighet 30+31+32 + utgående moms vid import 60+61+62) − ingående moms (ruta 48). Positive → moms att betala. Negative → moms att återfå.
2. Trace the numbers back to the ledger
The declaration is a pure projection of the general ledger: every ruta is the aggregated balance of its BAS accounts over the period. There is no separate reconciliation object in the response — the rutor are the ledger balances. The account → ruta mapping (SKV 4700) is:
2611: Utgående moms 25% → ruta 102614: Utgående moms vid omvänd skattskyldighet → ruta 302615: Utgående moms vid import → ruta 602621: Utgående moms 12% → ruta 112631: Utgående moms 6% → ruta 122641: Ingående moms → ruta 482645: Ingående moms på utländska förvärv → ruta 48
To sanity-check the figures, use the supporting fields the response already returns:
invoiceCount/transactionCount: how many posted entries fed the period.breakdown.invoices: the sales-side rutor (05/06/07/10/11/12/39/40) plus per-rate bases (base25/base12/base6).breakdown.transactions.ruta48/breakdown.receipts.ruta48: where the input VAT in ruta 48 came from (breakdown.transactions.ruta48mirrors the fullrutor.ruta48).breakdown.reverseCharge: the reverse-charge purchase bases (ruta 20-24) and the self-assessed output VAT (ruta 30-32).
For a byte-level tie-out against raw account balances, pull the general ledger report filtered to the 26xx accounts and compare each account against the mapping above.
3. The 2026-04-01 livsmedel rate change
Important compliance moment in April 2026. The VAT rate on livsmedel (groceries) drops from 12% → 6% effective 2026-04-01 (Prop. 2025/26:55). The decisive date under ML (2023:200) is the tidpunkt för skattskyldighetens inträde, whose timing rules live in 7 kap. 3–13 §§. The general rule for goods is the supply date (delivery), not the invoice date — but a förskott (advance payment) received before delivery triggers the VAT point, and pins the rate, at the time of that payment for the amount paid. So a food förskott paid in March for an April delivery is taxed at 12%, even though delivery is after the cutover. Drive the treatment off the taxable event (delivery, or earlier advance payment), not the delivery date alone.
- Choose the VAT treatment from the supply date, and record
delivery_datewhen it differs frominvoice_date. You set the rate per line viavat_treatment:reduced_6books to2631(6%) andreduced_12books to2621(12%). The engine does not infer the rate fromdelivery_date— it books to the account implied by the treatment you send — so for food supplied ≥ 2026-04-01 sendreduced_6, andreduced_12for food supplied before the cutover, regardless of when the invoice was issued. For continuous or subscription food supplies (e.g. a weekly grocery box), the trigger point is the date when each individual delivery's skattskyldighet inträder: confirm against ML 1 kap 3 § rather than assuming the rule equals a single delivery date. - The classic edge case: food delivered in March, invoiced in April. The supply date (March) governs, so the line must be booked at 12% (
reduced_12) even though the invoice is dated April. Drive the treatment off the supply date — not the invoice date — and recorddelivery_dateon the invoice for the audit trail. Do this for every food-line item in March-April 2026 invoices: the cost of explicit data is zero; the cost of a mis-booked verifikation is a manual rectification + a momsdeklaration adjustment. - If you omit
delivery_date, the invoice carries no explicit supply date and you are implicitly treating the invoice date as the supply date — fine for one-off service supplies where delivery and invoice coincide. Long-running service contracts (subscriptions, ongoing maintenance) have per-delprestation skattskyldighet under ML 1 kap 3 § and need an explicitdelivery_dateper billing cycle. Goods that straddle the cutover always need an explicitdelivery_date.
The VAT declaration for April 2026 onwards will show split balances on rutor 11/12: pre-2026-04-01 food sales remain on ruta 11 (12%), post-cutover food sales appear on ruta 12 (6%). The breakdown.invoices per-rate bases (base12 / base6) let you see the split, so you can catch any post-cutover line still booked at 12% before you submit.
4. Pre-flight: voucher gaps
BFNAR 2013:2 kap 6-7 §§ requires every voucher gap to have a documented explanation. Skatteverket may ask why F-2026-0042 exists when no F-2026-0041 is on the books. Check before declaring:
curl "https://app.gnubok.se/api/v1/companies/$COMPANY_ID/compliance/check?type=voucher_gaps&fiscal_period_id=$FISCAL_PERIOD_ID" \
-H "Authorization: Bearer gnubok_sk_test_..."
The check takes the period's UUID as fiscal_period_id (not a YYYY-MM string); get it from GET /fiscal-periods. Supported type values are voucher_gaps and year_end_readiness. If gaps exist, file an explanation via POST /voucher-gap-explanations BEFORE submitting the declaration: gaps without explanations are a compliance audit finding.
5. Pre-flight: locked period
The declaration is computed from posted entries in the period. If the period is still open and you have draft entries that should be in this declaration, commit them before declaring. After declaring, lock the period:
curl -X POST "https://app.gnubok.se/api/v1/companies/$COMPANY_ID/fiscal-periods/$PERIOD_ID/lock" \
-H "Authorization: Bearer gnubok_sk_test_..." \
-H "Idempotency-Key: $(uuidgen)"
Locking is reversible until you close the period, but the unlock endpoint is not exposed in v1: reverse a lock from the dashboard. Closing the period is irreversible per BFL 5 kap 8 §; only close after the declaration is submitted AND any audit-period grace window has passed.
6. Manual submission to Skatteverket
v1 does not submit the declaration. The receipt-quality JSON above is what you transcribe into Skatteverket Mina Sidor (or feed into your own Skatteverket-integration tooling, gated by BankID: handled by the optional skatteverket extension, not the public REST API).
For audit-trail completeness, capture the submission confirmation number from Skatteverket. v1 does not expose a field for a Skatteverket submission reference on the fiscal period, so record it in your own system of record (or your integration's audit log) alongside the period it belongs to.
EU and reverse-charge handling
Sales of services to other EU businesses (vat_treatment: 'reverse_charge') book to account 3308 and appear on ruta 39, bypassing the output-moms accounts (no entry on 26xx). The customer accounts for moms in their own country. (Use export instead — account 3305, ruta 40 — for services sold outside the EU.)
Purchases of services from other EU businesses (supplier_invoice with vat_treatment: 'reverse_charge') put the purchase base on ruta 21 (tjänster från EU). The engine books a calculated 25% reverse-charge output on account 2614 (→ ruta 30) AND an input-moms entry on 2645 (→ ruta 48): net zero impact on cash flow when full avdragsrätt applies, full traceability on the declaration. For blandad verksamhet (mixed-activity companies with partial avdragsrätt per HFD 2023 ref. 45), the deductible portion of the 2645 leg must be restricted at booking time (e.g. by splitting the non-deductible share onto account 2649, blandad verksamhet) — there is no company-level deduction-percent setting that does this for you; otherwise the full input-moms reaches ruta 48 and over-declares the deduction.
Imports from outside EU book through a customs-clearance flow — there is no import vat_treatment value; the customs (Tullverket) invoice is what posts the moms, not the supplier invoice itself. The beskattningsunderlag lands on ruta 50 and the calculated import output VAT on rutor 60/61/62 (25/12/6%). Coverage of this is in the Supplier invoices reference.
Common pitfalls
- Decimals vs hela kronor: truncate öre, do not round. The API returns rutor as decimal numbers (öre preserved). Skatteverket Mina Sidor and SRU filings accept only hela kronor; the rule per SFL 22 kap 1 § is truncation (drop öre), NOT half-up rounding. Use
Math.floorfor positive amounts when transcribing. Truncate at the rendering / submission boundary, not in storage. - Don't compute mid-month. The figures are only meaningful for a complete month; calling with
period_type=monthly&year=2026&period=4mid-April returns the partial state. The endpoint doesn't refuse partial periods, so this is on the integrator. - Mixed-rate invoices. A single invoice with both 25% and 12% items lands on both
2611and2621. The declaration handles this correctly because the per-line VAT rate is preserved in the engine; integrations that flatten to a single header rate will mis-declare. - Reverse-charge invoices in the wrong ruta. A B2B sale to an EU customer with a missing/unvalidated VAT number does NOT qualify for reverse charge: those go to ruta 05 with normal 25% moms. Validate the customer's VAT number against VIES before issuing the invoice — note that VAT-number validation is a dashboard feature and is not exposed as a public v1 endpoint, so do the VIES check in your own flow.
Next steps
- Year-end closing: once all 12 monthly declarations are filed, close the fiscal year.
- Run payroll: moms and AGI are independent; both need to be filed monthly.
- Reports reference: every report, every parameter.