Jämför Bokföring
Teknik

Fortnox API: komplett guide för utvecklare 2026

Fortnox API-guide: OAuth2-flow, endpoints, Python-exempel, rate limits och vanliga fel. Skapa faktura, hämta kunder och bygg integrationer.

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

Fortnox API är i praktiken det enda produktionsmogna REST-API:et för svensk bokföring med ordentlig dokumentation, stort partnerekosystem och ett rate limit som går att leva med. Det är också det API jag bygger mot i 80 % av uppdragen där en kund vill automatisera fakturering eller läsa in verifikat från ett externt system.

Den här guiden täcker det du behöver för att komma igång på en eftermiddag: OAuth2-flödet, hur du skapar en faktura, hanterar rate limits och undviker de vanligaste felen. Allt testat mot faktisk Fortnox-tenant i april 2026.

Snabbstart på 10 minuter

Innan du ens öppnar en editor behöver du tre saker:

  1. En Fortnox-licens (din egen demo-tenant räcker — 6 mån gratis via kod NYSTARTAD).
  2. Ett utvecklarkonto på developer.fortnox.se — här registrerar du din integration och får client_id + client_secret.
  3. En kund (eller test-tenant) som godkänner din app — utan det får du inte ut någon access_token.

Integrationen är en OAuth2-app. Fortnox är ovanligt strikta med att man registrerar exakta scopes i förväg (t.ex. invoice, customer, bookkeeping). Begär bara det du faktiskt använder — det är svårare att lägga till scopes senare utan att användaren godkänner om.

OAuth2-flow: så funkar det i Fortnox

Fortnox använder en lätt modifierad variant av OAuth2 Authorization Code-flödet. Stegen är:

  1. Din app skickar användaren till Fortnox authorization-URL med client_id, scope och state.
  2. Användaren loggar in och godkänner scopes.
  3. Fortnox redirectar tillbaka till din redirect_uri med en code-parameter.
  4. Din backend byter code mot access_token + refresh_token via POST https://apps.fortnox.se/oauth-v1/token.
  5. access_token är giltig i 1 timme. refresh_token används för att förnya.
import requests
import base64

CLIENT_ID = "din-client-id"
CLIENT_SECRET = "din-client-secret"
REDIRECT_URI = "https://dinapp.se/callback"

def exchange_code_for_token(code: str) -> dict:
    auth = base64.b64encode(
        f"{CLIENT_ID}:{CLIENT_SECRET}".encode()
    ).decode()
    r = requests.post(
        "https://apps.fortnox.se/oauth-v1/token",
        headers={
            "Authorization": f"Basic {auth}",
            "Content-Type": "application/x-www-form-urlencoded",
        },
        data={
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": REDIRECT_URI,
        },
        timeout=15,
    )
    r.raise_for_status()
    return r.json()

Svaret ser ut ungefär så här:

{
  "access_token": "eyJ0eXAiOiJKV1Qi...",
  "refresh_token": "a1b2c3d4...",
  "expires_in": 3600,
  "token_type": "Bearer",
  "scope": "invoice customer bookkeeping"
}

Viktig detalj: spara refresh_token direkt. När jag testade förra veckan hände det att en kund tappade sin refresh_token och vi fick köra om hela auktorisationsflödet med slutanvändaren. Refresh_token går att återanvända tills användaren återkallar tillstånd.

Autentisering: access_token och refresh

Alla API-anrop skickas med en Authorization: Bearer {access_token}-header mot https://api.fortnox.se/3/. När token löpt ut får du 401 — då förnyar du så här:

def refresh_access_token(refresh_token: str) -> dict:
    auth = base64.b64encode(
        f"{CLIENT_ID}:{CLIENT_SECRET}".encode()
    ).decode()
    r = requests.post(
        "https://apps.fortnox.se/oauth-v1/token",
        headers={
            "Authorization": f"Basic {auth}",
            "Content-Type": "application/x-www-form-urlencoded",
        },
        data={
            "grant_type": "refresh_token",
            "refresh_token": refresh_token,
        },
        timeout=15,
    )
    r.raise_for_status()
    return r.json()

Det vanligaste felet jag stött på är att man glömmer uppdatera den sparade refresh_token efter refresh — Fortnox kan rotera den. Skriv alltid över båda i din databas.

Endpoints översikt

Fortnox /3/-API:et är brett. Här är de endpoints du kommer att använda oftast:

EndpointBeskrivningMetoder
/3/invoicesKundfakturorGET, POST, PUT
/3/customersKunderGET, POST, PUT, DELETE
/3/suppliersLeverantörerGET, POST, PUT
/3/vouchersVerifikatGET, POST
/3/accountsKontoplanGET, PUT
/3/articlesArtiklar/produkterGET, POST, PUT, DELETE
/3/ordersOrderläggningGET, POST, PUT
/3/offersOfferterGET, POST
/3/financialyearsRäkenskapsårGET
/3/employeesAnställdaGET, POST, PUT

Fullständig referens finns på apps.fortnox.se/apidocs. API:et returnerar både JSON och XML — ange Accept: application/json i alla anrop om du kör Python eller JavaScript.

Skapa en faktura (Python + cURL)

Det här är hello-world för Fortnox-integrationer. Du behöver först en kund (CustomerNumber). Här antar vi att kunden redan finns.

Python

import requests

ACCESS_TOKEN = "din-access-token"

def create_invoice(customer_number: str, rows: list) -> dict:
    payload = {
        "Invoice": {
            "CustomerNumber": customer_number,
            "InvoiceDate": "2026-04-15",
            "DueDate": "2026-05-15",
            "InvoiceRows": rows,
        }
    }
    r = requests.post(
        "https://api.fortnox.se/3/invoices",
        headers={
            "Authorization": f"Bearer {ACCESS_TOKEN}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        },
        json=payload,
        timeout=15,
    )
    if r.status_code == 429:
        raise RuntimeError("Rate limit — vänta 5 sek och försök igen")
    r.raise_for_status()
    return r.json()

rows = [
    {
        "ArticleNumber": "KONSULT",
        "Description": "Konsulttimmar april",
        "DeliveredQuantity": 10,
        "Price": 1200,
        "VAT": 25,
    }
]

invoice = create_invoice("1", rows)
print(invoice["Invoice"]["DocumentNumber"])

cURL

curl -X POST https://api.fortnox.se/3/invoices \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "Invoice": {
      "CustomerNumber": "1",
      "InvoiceDate": "2026-04-15",
      "DueDate": "2026-05-15",
      "InvoiceRows": [
        {"ArticleNumber": "KONSULT", "DeliveredQuantity": 10, "Price": 1200, "VAT": 25}
      ]
    }
  }'

Fortnox returnerar hela fakturan med DocumentNumber, Total, Balance m.fl. Spara DocumentNumber — det är den primära nyckeln du använder för att uppdatera, skicka eller makulera fakturan senare.

Hämta kunder

def list_customers(page: int = 1, limit: int = 100) -> dict:
    r = requests.get(
        f"https://api.fortnox.se/3/customers",
        headers={
            "Authorization": f"Bearer {ACCESS_TOKEN}",
            "Accept": "application/json",
        },
        params={"page": page, "limit": limit},
        timeout=15,
    )
    r.raise_for_status()
    return r.json()

data = list_customers()
for c in data["Customers"]:
    print(c["CustomerNumber"], c["Name"])

Paginering är något underdokumenterad i Fortnox. MetaInformation i svaret innehåller @TotalResources, @TotalPages och @CurrentPage — använd dem för att iterera. Max limit är 500, men jag brukar köra 100 för att hålla svarsstorleken hanterbar.

cURL-variant för enkel koll

curl https://api.fortnox.se/3/customers?limit=5 \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Accept: application/json"

Webhooks

Fortnox Webhooks ligger i beta enligt dokumentationen 2026 och stöder i dagsläget ett begränsat antal händelser — främst invoice.created, invoice.paid och voucher.created. Du registrerar webhooks via /3/webhooks-endpointen.

def register_webhook(url: str, event: str) -> dict:
    payload = {
        "Webhook": {
            "Url": url,
            "Event": event,
        }
    }
    r = requests.post(
        "https://api.fortnox.se/3/webhooks",
        headers={
            "Authorization": f"Bearer {ACCESS_TOKEN}",
            "Content-Type": "application/json",
        },
        json=payload,
        timeout=15,
    )
    r.raise_for_status()
    return r.json()

Webhooks signeras med en HMAC-header — verifiera alltid signaturen innan du litar på payloaden. Sekreten får du när webhooken registreras.

Nackdel som är värd att nämna: Fortnox webhooks är långsammare och mindre kompletta än t.ex. Stripes. För kritiska flöden (bokföring av inbetalningar) kompletterar jag alltid med en polling-job som körs var 15:e minut som failsafe.

Rate limits och felhantering

Fortnox rate limit enligt dokumentationen 2026 är 25 requests per 5 sekunder per access_token — alltså ungefär 300 req/min. Det låter generöst men är lätt att träffa om du bulk-importerar kunder eller fakturor.

Felkoderna du kommer se:

StatusBetydelseÅtgärd
401Access_token utgången eller ogiltigKör refresh_token-flow
403Saknar scopeRe-auktorisera med rätt scope
404Resurs finns inteKolla DocumentNumber/CustomerNumber
429Rate limitBacka av, vänta 5 sek
400ValideringsfelLäs ErrorInformation.message i svaret
500Fortnox-felRetry med exponential backoff

Enkel retry-wrapper:

import time

def with_retry(fn, max_retries: int = 5):
    for attempt in range(max_retries):
        try:
            return fn()
        except requests.HTTPError as e:
            status = e.response.status_code
            if status == 429:
                time.sleep(5 * (attempt + 1))
            elif status >= 500:
                time.sleep(2 ** attempt)
            else:
                raise
    raise RuntimeError("Max retries nådda")

Vanligaste 400-felet jag stött på är att CustomerNumber skickas som int istället för string. Fortnox är petiga med typer — alla ID-fält ska vara strängar.

Begränsningar — var ärlig

Fortnox API är bra, men inte perfekt:

  • Ingen separat sandbox. Du testar mot en riktig (men gratis demo-) tenant. Jämför med Visma eEkonomi som har dedikerad sandbox.
  • Webhooks är fortfarande begränsade. För realtid i produktion kompletterar du med polling.
  • Paginering är inkonsekvent mellan olika endpoints — vissa använder @TotalPages, andra returnerar bara data.
  • Svarsformat har legacy-XML-arv — fältnamn är PascalCase och nästlade i konstiga objektnivåer (Invoice.InvoiceRows.InvoiceRow).

För en utvecklarvinkel på hur Fortnox står sig mot andra bokföringsprogram, se vår jämförelse av bokföringsprograms API:er.

FAQ

Är Fortnox API gratis?

API-åtkomsten i sig är gratis — men du behöver en Fortnox-licens (från 209 kr/mån, se Fortnox-prisanalys) för att ha en tenant att integrera mot.

Finns det en sandbox?

Nej. Fortnox har ingen separat sandbox, utan du kör mot en riktig tenant (ofta demo-tenant med 6 mån gratis). Det är en svaghet jämfört med Visma eEkonomi.

Hur länge gäller en access_token?

1 timme (3600 sekunder). Refresh_token är långlivad och används för att förnya tills användaren återkallar tillstånd.

Vilka språk har officiella SDK:er?

Fortnox publicerar inget officiellt SDK. Det finns community-paket för Python (fortnox-python), PHP och Node — kvaliteten varierar. Jag bygger oftast direkt mot REST-API:et eftersom det är enklare att felsöka.

Kan jag automatisera hela bokföringen via API?

I princip ja — verifikat (/3/vouchers), fakturor och kontoplan är alla tillgängliga. Men slutlig deklaration och årsredovisning sker fortfarande i Fortnox-UI. Se vår guide automatisera fakturering för ett komplett exempel.

Vad kostar det att bygga en integration?

Utvecklartid. Fortnox tar ingen API-avgift för standardintegrationer. Om du paketerar din integration i deras Fortnox Appstore får Fortnox provision.


Fortnox API är det säkraste valet om du bygger en svensk bokföringsintegration idag. Dokumentationen är inte perfekt och webhooks är begränsade, men ekosystemet och stabiliteten slår allt annat på den svenska marknaden. Börja med OAuth2-flödet, skapa en faktura, och bygg ut därifrån.

Läs vidare