Fortnox API — kodexempelKodexempel 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
| Nr | Datum | Kund | Totalt |
|---|---|---|---|
| 1001 | 3 jun | Kvarnen AB | 12 500 |
| 1004 | 9 jun | Nordic Bygg | 6 250 |
| 1007 | 17 jun | Lindqvist & Co | 3 750 |
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
Begärd via Claude · Acme AB
| Fält | I Fortnox nu | Efter godkännande |
|---|---|---|
| Kundnummer | I Fortnox nu— | Efter godkännande1 |
| Fakturarad · Konsulttimmar | I Fortnox nu— | Efter godkännande1 200,00 |
| Moms 25 % | I Fortnox nu— | Efter godkännande300,00 |
| Totalt | I Fortnox nu— | Efter godkännande1 500,00 |
Inget skrivs till Fortnox förrän du godkänner, om inte företaget har slagit på automatiskt godkännande.
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
- 01Hämta obetalda fakturor
- 02Skapa en faktura
- 03Bläddra igenom leverantörer
- 04Hantera 429 med backoff
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.
“visa obetalda fakturor”“skapa faktura”“lista leverantörer”“ingen 429-hantering behövs — vi sköter retry”