Jämför Bokföring
Teknik

Automatisera fakturering med Fortnox: komplett tutorial 2026

Steg-för-steg: bygg ett Python-script som skapar och skickar fakturor via Fortnox API. OAuth2, felhantering och produktionstips ingår.

Av Jämför Bokföring-redaktionen Uppdaterad 2026-04-15

Om du fakturerar mer än 20-30 gånger i månaden manuellt i Fortnox har du redan slösat en halv arbetsdag som du kunde byggt bort på en eftermiddag. Den här guiden visar hur du bygger en riktig Fortnox-integration som skapar, bokför och skickar fakturor via deras REST-API — från OAuth2 till produktion.

Jag har byggt den här flödet åt tre olika kunder under 2025-26. Varje gång har den sparat mellan 8 och 30 timmar i månaden. Koden nedan är avskalad men fungerar — allt är testat mot en Fortnox-tenant i april 2026.

Problemet: manuell fakturering skalar inte

De typiska tecknen att det är dags att automatisera:

  • Du fakturerar samma kunder varje månad med i princip samma rader
  • Ditt externa system (time-tracker, e-handelsplattform, SaaS) har redan all data
  • Du kopierar från Excel till Fortnox regelbundet
  • Du har glömt fakturera en kund minst en gång senaste halvåret

Automatisering via Fortnox API löser alla fyra. Alternativet — Zapier, Make eller liknande no-code-verktyg — funkar i enkla fall men tappar i precision, felhantering och kostnad när volymen ökar.

OAuth-setup: det här behövs innan du skriver kod

  1. Skapa utvecklarkontodeveloper.fortnox.se
  2. Registrera din app och få client_id + client_secret
  3. Välj scopes: för ett faktureringsflöde räcker invoice + customer + article
  4. Sätt redirect_uri till din backend (https://dinapp.se/oauth/callback eller http://localhost:8000/callback under utveckling)
  5. Kör en gång Authorization Code-flödet för att få ut refresh_token för den tenant du ska integrera mot

Första gången är manuell: öppna authorization-URL:en i webbläsaren, godkänn scopes, kopiera ut code från redirect-URL:en och byt mot tokens. Spara refresh_token i din databas — det är den du använder för evigt framåt (tills användaren återkallar tillstånd).

Detaljerad OAuth2-implementering finns i vår Fortnox API-guide. Här fokuserar vi på själva faktureringsscriptet.

Komplett Python-script: skapa och skicka faktura

Nedan är ett komplett, kopierbart script som läser kunddata från en lista (eller CSV, databas — byt ut), skapar fakturor i Fortnox och markerar dem som skickade. Felhantering för 401, 429 och 400 ingår.

"""
fortnox_faktura_automation.py
Skapa och skicka fakturor via Fortnox API.
"""
import base64
import json
import time
from dataclasses import dataclass
from typing import Optional

import requests

# ---------- Konfiguration ----------
CLIENT_ID = "din-client-id"
CLIENT_SECRET = "din-client-secret"
TOKEN_FILE = "fortnox_tokens.json"  # byt mot databas i produktion

API_BASE = "https://api.fortnox.se/3"
OAUTH_URL = "https://apps.fortnox.se/oauth-v1/token"


@dataclass
class FakturaRad:
    artikelnr: str
    beskrivning: str
    antal: float
    pris: float
    moms: int = 25  # procent


# ---------- Token management ----------
def load_tokens() -> dict:
    with open(TOKEN_FILE) as f:
        return json.load(f)


def save_tokens(tokens: dict) -> None:
    with open(TOKEN_FILE, "w") as f:
        json.dump(tokens, f)


def refresh_access_token() -> str:
    tokens = load_tokens()
    auth = base64.b64encode(
        f"{CLIENT_ID}:{CLIENT_SECRET}".encode()
    ).decode()
    r = requests.post(
        OAUTH_URL,
        headers={
            "Authorization": f"Basic {auth}",
            "Content-Type": "application/x-www-form-urlencoded",
        },
        data={
            "grant_type": "refresh_token",
            "refresh_token": tokens["refresh_token"],
        },
        timeout=15,
    )
    r.raise_for_status()
    new = r.json()
    # VIKTIGT: Fortnox kan rotera refresh_token
    save_tokens({
        "access_token": new["access_token"],
        "refresh_token": new["refresh_token"],
    })
    return new["access_token"]


def get_access_token() -> str:
    return load_tokens().get("access_token", "")


# ---------- API-anrop med retry ----------
def fortnox_request(
    method: str,
    path: str,
    payload: Optional[dict] = None,
    attempt: int = 0,
) -> dict:
    token = get_access_token()
    r = requests.request(
        method,
        f"{API_BASE}{path}",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        },
        json=payload,
        timeout=20,
    )
    if r.status_code == 401 and attempt == 0:
        refresh_access_token()
        return fortnox_request(method, path, payload, attempt=1)
    if r.status_code == 429:
        time.sleep(5)
        return fortnox_request(method, path, payload, attempt=attempt + 1)
    if r.status_code == 400:
        error = r.json().get("ErrorInformation", {})
        raise ValueError(
            f"Valideringsfel: {error.get('message')} "
            f"({error.get('code')})"
        )
    r.raise_for_status()
    return r.json()


# ---------- Faktureringsflöde ----------
def skapa_faktura(
    kundnummer: str,
    rader: list[FakturaRad],
    fakturadatum: str,
    forfallodatum: str,
) -> dict:
    payload = {
        "Invoice": {
            "CustomerNumber": kundnummer,
            "InvoiceDate": fakturadatum,
            "DueDate": forfallodatum,
            "InvoiceRows": [
                {
                    "ArticleNumber": r.artikelnr,
                    "Description": r.beskrivning,
                    "DeliveredQuantity": r.antal,
                    "Price": r.pris,
                    "VAT": r.moms,
                }
                for r in rader
            ],
        }
    }
    return fortnox_request("POST", "/invoices", payload)


def skicka_faktura(dokumentnummer: str) -> dict:
    # Fortnox har en "action"-endpoint för att markera som skickad
    return fortnox_request(
        "PUT",
        f"/invoices/{dokumentnummer}/email",
    )


# ---------- Main ----------
def main():
    # Exempel: månadsfakturering av konsulttimmar
    konsulttid = [
        {"kund": "1", "timmar": 10, "projekt": "Website"},
        {"kund": "2", "timmar": 8.5, "projekt": "API-integration"},
        {"kund": "3", "timmar": 12, "projekt": "Support"},
    ]

    for jobb in konsulttid:
        try:
            rader = [
                FakturaRad(
                    artikelnr="KONSULT",
                    beskrivning=f"Konsulttid april — {jobb['projekt']}",
                    antal=jobb["timmar"],
                    pris=1200,
                )
            ]
            resultat = skapa_faktura(
                kundnummer=jobb["kund"],
                rader=rader,
                fakturadatum="2026-04-30",
                forfallodatum="2026-05-30",
            )
            dokumentnr = resultat["Invoice"]["DocumentNumber"]
            print(f"Faktura skapad: {dokumentnr} för kund {jobb['kund']}")

            skicka_faktura(dokumentnr)
            print(f"  → Skickad via email")
        except ValueError as e:
            print(f"FEL för kund {jobb['kund']}: {e}")
        except Exception as e:
            print(f"OVÄNTAT FEL för kund {jobb['kund']}: {e}")


if __name__ == "__main__":
    main()

Kör det här månatligt via cron eller systemd och du har en färdig automation. Byt ut konsulttid-listan mot data från din time-tracker, din databas eller en CSV, så är du i mål.

Felsökning: vanliga 401/429-fel

401 Unauthorized

Orsak: Access_token har löpt ut (giltig 1 timme) eller scope saknas.

Lösning: Scriptet ovan hanterar automatiskt token-refresh via fortnox_request-wrappern. Om du får 401 trots refresh betyder det att refresh_token är ogiltig — användaren måste auktorisera om.

Debug-tips: dekoda JWT-delen av access_token på jwt.io för att se vilka scopes du faktiskt har.

429 Too Many Requests

Orsak: Du överskrider 25 req/5 sek.

Lösning: Backa av 5 sekunder och retry. Scriptet gör det automatiskt. I batch-jobs, lägg till en time.sleep(0.2) mellan anrop som preventiv åtgärd.

400 Bad Request

Vanligaste fel jag stött på i produktion:

  • “Customer not found”CustomerNumber skickas som int istället för str
  • “Article not found” — artikeln måste finnas i Fortnox innan du refererar den
  • “Invalid VAT”VAT ska vara 25, 12, 6 eller 0, inte 0.25
  • “Invalid date” — datum ska vara YYYY-MM-DD, inte svensk DD/MM/YYYY

403 Forbidden

Saknar scope för den endpoint du anropar. Du måste köra om auktoriseringsflödet med rätt scopes — de går inte att lägga till utan användarens godkännande.

Produktion-tips

När du går från proof-of-concept till produktion:

  1. Lagra tokens i databas, inte fil. JSON-filen ovan är för utveckling. Använd Postgres/MySQL med kryptering på kolumnerna.
  2. Logga alla API-anrop med korrelations-ID. När en kund hävdar att en faktura saknas vill du snabbt kunna återskapa exakt vad som skickades och fick för svar.
  3. Idempotens. Om scriptet kraschar mitt i en batch — hur undviker du dubbla fakturor vid retry? Lägg till en YourReference med ett internt jobb-ID och kolla först om fakturan redan finns.
  4. Monitorering. Alert när refresh_token slutar fungera eller när 429-frekvensen ökar ovanligt.
  5. Timeout + retry med exponential backoff på 5xx-fel.
  6. Testa mot demo-tenant innan du kör mot produktion. Fortnox har ingen sandbox, men en gratis demo-tenant är nästa bästa.

Kompletterande läsning: Fortnox API-guide och Fortnox prisanalys.

FAQ

Kan jag skicka e-faktura (Peppol) via API:et?

Ja. Fortnox stödjer Peppol via /3/invoices-endpointen — sätt InvoiceType till rätt värde och ange Peppol-identifieraren på kunden.

Hur hanterar jag kreditfakturor?

Skapa en faktura med negativa belopp och sätt Credit till true + CreditInvoiceReference till original-DocumentNumber.

Kan scriptet också bokföra inbetalningar?

Ja, men det är ett separat flöde via /3/voucherseries och /3/vouchers. Rekommendationen är att låta Fortnox banksynk sköta inbetalningar automatiskt och bara använda API:et för fakturering.

Får jag skicka fakturor till utlandet?

Ja — sätt VATType till REVERSE_EU eller EXPORT och ange korrekt valuta i Currency-fältet. Moms hanteras automatiskt.

Vad kostar det att automatisera?

Utvecklartid (1-5 dagar för första versionen). Fortnox tar ingen extra API-avgift. Eventuella hostingkostnader för cron-jobbet är försumbara (under 50 kr/mån på t.ex. Hostup).

Finns det en färdig Zapier-integration istället?

Ja — Fortnox har officiell Zapier-app. Men Zapier kostar ofta mer än en egen integration efter några månader, och är mindre flexibel för edge cases.


Fakturering via Fortnox API är ett av de mest givande automatiseringsprojekten för svenska småföretag — låg komplexitet, hög tidsbesparing, direkt mätbar ROI. Scriptet ovan är en bra startpunkt. Lägg några dagar på felhantering, logg och monitorering, så har du en integration som håller i flera år.

Läs vidare