Hoppa till innehållet
FortnoxFortnox API — kodexempel

Kodexempel för Fortnox API

Fyra färdiga exempel mot Fortnox API, varje ett i både Python och TypeScript: hämta obetalda fakturor, skapa en faktura, pagera igenom leverantörsregistret och hantera 429 med backoff.

Alla fyra exempel autentiserar på samma sätt: en Bearer-token i Authorization-headern mot https://api.fortnox.se/3. Byt ut ACCESS_TOKEN (Python) eller accessToken (TypeScript) mot en riktig token innan du kör koden.

Den token skriver du inte in för hand — den kommer från Fortnox OAuth 2.0-flöde och förnyas med en refresh-token. Så fungerar autentiseringen steg för steg.

Hämta obetalda kundfakturor

GET /3/invoices med frågeparametern filter=unpaid hämtar bara obetalda fakturor — Fortnox OpenAPI-specifikation definierar filtret tillsammans med cancelled, fullypaid, unpaidoverdue och unbooked. Svaret är en Invoices-lista där varje post har fältet Total, så exemplet räknar antal och summerar direkt.

Python

import requests

ACCESS_TOKEN = "..."  # from the Fortnox OAuth flow, see /fortnox-api/auth

response = requests.get(
    "https://api.fortnox.se/3/invoices",
    headers={"Authorization": f"Bearer {ACCESS_TOKEN}"},
    params={"filter": "unpaid"},
)
response.raise_for_status()

invoices = response.json()["Invoices"]
total = sum(invoice["Total"] for invoice in invoices)
print(f"{len(invoices)} obetalda fakturor, totalt {total}")

TypeScript

const accessToken = "..."; // from the Fortnox OAuth flow, see /fortnox-api/auth

const response = await fetch(
  "https://api.fortnox.se/3/invoices?filter=unpaid",
  { headers: { Authorization: `Bearer ${accessToken}` } },
);
if (!response.ok) {
  throw new Error(`Fortnox request failed: ${response.status}`);
}

const { Invoices } = await response.json();
const total = Invoices.reduce((sum, invoice) => sum + invoice.Total, 0);
console.log(`${Invoices.length} unpaid invoices, totaling ${total}`);

Exempel · påhittade fakturor

ClaudeClaudeObetalda fakturor
Visa obetalda fakturor
MCP-verktyg · Fortnox kundfakturor
Obetalda
Kundfakturor · obetalda
NrDatumKundTotalt
10013 junKvarnen AB12 500
10049 junNordic Bygg6 250
100717 junLindqvist & Co3 750
3 fakturor · totalt22 500 kr
Fråga om din bokföring…
Anslutna servrar: Nordsynk
Claude
En läsning: svaret kommer direkt från Fortnox, utan klientfil och utan godkännande.

Skapa en kundfaktura

POST /3/invoices tar en Invoice-payload. Enligt den bundlade OpenAPI-specifikationen är CustomerNumber det enda obligatoriska fältet på Invoice-schemat — exemplet lägger även till en InvoiceRows-rad så fakturan går att skicka. Ett lyckat anrop svarar med statuskod 201 och den skapade fakturan.

Python

import requests

ACCESS_TOKEN = "..."  # from the Fortnox OAuth flow, see /fortnox-api/auth

payload = {
    "Invoice": {
        "CustomerNumber": "1",  # the only required field on the Invoice schema
        "InvoiceRows": [
            {"Description": "Konsulttimmar", "Price": 1200, "VAT": 25},
        ],
    }
}

response = requests.post(
    "https://api.fortnox.se/3/invoices",
    headers={"Authorization": f"Bearer {ACCESS_TOKEN}"},
    json=payload,
)
if response.status_code != 201:
    raise RuntimeError(f"Fortnox request failed: {response.status_code} {response.text}")

invoice = response.json()["Invoice"]
print(f"Created invoice {invoice['DocumentNumber']} for {invoice['Total']}")

TypeScript

const accessToken = "..."; // from the Fortnox OAuth flow, see /fortnox-api/auth

const response = await fetch("https://api.fortnox.se/3/invoices", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    Invoice: {
      CustomerNumber: "1", // the only required field on the Invoice schema
      InvoiceRows: [{ Description: "Konsulttimmar", Price: 1200, VAT: 25 }],
    },
  }),
});
if (!response.ok) {
  throw new Error(`Fortnox request failed: ${response.status}`);
}

const { Invoice } = await response.json();
console.log(`Created invoice ${Invoice.DocumentNumber} for ${Invoice.Total}`);

Exempel · påhittad faktura

Förhandsgranskning · Fortnox

Skapa kundfaktura till kund 1, 1 500 kr

Väntar på godkännande

Begärd via Claude · Acme AB

KundnummerI Fortnox nu—Efter godkännande1
Fakturarad · KonsulttimmarI Fortnox nu—Efter godkännande1 200,00
Moms 25 %I Fortnox nu—Efter godkännande300,00
TotaltI Fortnox nu—Efter godkännande1 500,00
GodkännAvvisa

Inget skrivs till Fortnox förrän du godkänner, om inte företaget har slagit på automatiskt godkännande.

En skrivning: du ser exakt vad som skapas och godkänner eller avvisar. Förhandsgranskningen kan inte redigeras.

Hantera paginering

GET /3/suppliers svarar med en Suppliers-lista utan sidmetadata i den bundlade specifikationen. Istället för att läsa ett TotalPages-fält som inte finns i schemat loopar exemplet på page och limit tills en sida kommer tillbaka kortare än limit.

Python

import requests

ACCESS_TOKEN = "..."  # from the Fortnox OAuth flow, see /fortnox-api/auth
PAGE_SIZE = 100

suppliers = []
page = 1
while True:
    response = requests.get(
        "https://api.fortnox.se/3/suppliers",
        headers={"Authorization": f"Bearer {ACCESS_TOKEN}"},
        params={"page": page, "limit": PAGE_SIZE},
    )
    response.raise_for_status()

    batch = response.json()["Suppliers"]
    suppliers.extend(batch)
    if len(batch) < PAGE_SIZE:
        break
    page += 1

print(f"{len(suppliers)} leverantörer")

TypeScript

const accessToken = "..."; // from the Fortnox OAuth flow, see /fortnox-api/auth
const PAGE_SIZE = 100;

const suppliers: unknown[] = [];
let page = 1;

while (true) {
  const response = await fetch(
    `https://api.fortnox.se/3/suppliers?page=${page}&limit=${PAGE_SIZE}`,
    { headers: { Authorization: `Bearer ${accessToken}` } },
  );
  if (!response.ok) {
    throw new Error(`Fortnox request failed: ${response.status}`);
  }

  const { Suppliers } = await response.json();
  suppliers.push(...Suppliers);
  if (Suppliers.length < PAGE_SIZE) break;
  page += 1;
}

console.log(`${suppliers.length} suppliers`);

Hantera 429 med backoff

HTTP 429 betyder att kvoten (25 anrop per 5 sekunder) är nådd. Wrappern nedan använder samma standardvärden som vår produktionsklient: tre återförsök, exponentiell backoff från 1 till 30 sekunder med jitter, och den respekterar Retry-After-headern när Fortnox skickar en.

Python

import random
import time
import requests

MAX_RETRIES = 3
BASE_DELAY_S = 1
MAX_DELAY_S = 30

def fortnox_get(path, access_token, **params):
    for attempt in range(MAX_RETRIES + 1):
        response = requests.get(
            f"https://api.fortnox.se/3{path}",
            headers={"Authorization": f"Bearer {access_token}"},
            params=params,
        )
        if response.status_code != 429:
            response.raise_for_status()
            return response.json()

        if attempt == MAX_RETRIES:
            response.raise_for_status()

        retry_after = response.headers.get("Retry-After")
        delay = float(retry_after) if retry_after else min(BASE_DELAY_S * 2 ** attempt, MAX_DELAY_S)
        time.sleep(delay + random.uniform(0, delay * 0.5))  # jitter

ACCESS_TOKEN = "..."  # from the Fortnox OAuth flow, see /fortnox-api/auth
data = fortnox_get("/invoices", ACCESS_TOKEN, filter="unpaid")

TypeScript

const MAX_RETRIES = 3;
const BASE_DELAY_MS = 1_000;
const MAX_DELAY_MS = 30_000;

async function fortnoxGet(path: string, accessToken: string): Promise<unknown> {
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
    const response = await fetch(`https://api.fortnox.se/3${path}`, {
      headers: { Authorization: `Bearer ${accessToken}` },
    });

    if (response.status !== 429) {
      if (!response.ok) {
        throw new Error(`Fortnox request failed: ${response.status}`);
      }
      return response.json();
    }

    if (attempt === MAX_RETRIES) {
      throw new Error("Fortnox rate limit: retries exhausted");
    }

    const retryAfter = response.headers.get("Retry-After");
    const backoff = Math.min(BASE_DELAY_MS * 2 ** attempt, MAX_DELAY_MS);
    const delayMs = retryAfter ? Number(retryAfter) * 1000 : backoff;
    await new Promise((resolve) => setTimeout(resolve, delayMs + Math.random() * delayMs * 0.5));
  }

  throw new Error("Fortnox rate limit: retries exhausted");
}

const accessToken = "..."; // from the Fortnox OAuth flow, see /fortnox-api/auth
const data = await fortnoxGet("/invoices?filter=unpaid", accessToken);

Eller hoppa över koden

Nordsynk kopplar en AI-assistent direkt till Fortnox över MCP. Samma fyra uppgifter blir en rad text istället för en klientfil att underhålla:

Samma fyra uppgifter · två sätt

Med eget skript
  1. 01Hämta obetalda fakturor
  2. 02Skapa en faktura
  3. 03Bläddra igenom leverantörer
  4. 04Hantera 429 med backoff
Med Nordsynk
› Visa obetalda fakturor

Nordsynk kopplar en AI-assistent direkt till Fortnox via MCP. De fyra uppgifterna blir en rad text i stället för en klientfil att underhålla.

Retries och 429 är redan hanterade, så det du skriver är en mening.
  • “visa obetalda fakturor”
  • “skapa faktura”
  • “lista leverantörer”
  • “ingen 429-hantering behövs — vi sköter retry”