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.
Fortnox API är det mest utbyggda REST-API:et bland de svenska bokföringsprogrammen: öppen dokumentation, OAuth2, bredast endpoint-täckning och det största partnerekosystemet. Det finns ett undantag värt att känna till — Spiris är det enda av programmen som dessutom har en sandbox-miljö. Fortnox har ingen, så all utveckling sker mot en riktig eller demo-tenant.
Den här guiden täcker det du behöver för att komma igång: OAuth2-flödet, hur du skapar en faktura, hanterar rate limits och undviker de vanligaste fallgroparna. Kodexemplen bygger på Fortnox publika API-dokumentation — verifiera alltid mot den aktuella versionen på apps.fortnox.se/apidocs innan du bygger vidare, eftersom endpoints och fältnamn ändras över tid.
Snabbstart på 10 minuter
Innan du ens öppnar en editor behöver du tre saker:
- En Fortnox-licens (din egen demo-tenant räcker — 6 mån gratis via kod NYSTARTAD).
- Ett utvecklarkonto på developer.fortnox.se — här registrerar du din integration och får
client_id+client_secret. - 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:
- Din app skickar användaren till Fortnox authorization-URL med
client_id,scopeochstate. - Användaren loggar in och godkänner scopes.
- Fortnox redirectar tillbaka till din
redirect_urimed encode-parameter. - Din backend byter
codemotaccess_token+refresh_tokenviaPOST https://apps.fortnox.se/oauth-v1/token. access_tokenär giltig i 1 timme.refresh_tokenanvä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, innan du gör något annat med svaret. Tappar du bort den finns ingen väg tillbaka utan att köra hela auktorisationsflödet igen — vilket kräver att slutanvändaren loggar in och godkänner på nytt. Refresh_token går att återanvända tills användaren återkallar tillståndet.
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()
En vanlig fallgrop: Fortnox kan rotera refresh_token vid varje förnyelse. Glömmer du skriva över den sparade kopian slutar nästa refresh att fungera, och du måste köra hela auktorisationsflödet igen med slutanvändaren. Skriv därför alltid över båda tokens i din databas i samma transaktion.
Endpoints översikt
Fortnox /3/-API:et är brett. Här är de endpoints du kommer att använda oftast:
| Endpoint | Beskrivning | Metoder |
|---|---|---|
/3/invoices | Kundfakturor | GET, POST, PUT |
/3/customers | Kunder | GET, POST, PUT, DELETE |
/3/suppliers | Leverantörer | GET, POST, PUT |
/3/vouchers | Verifikat | GET, POST |
/3/accounts | Kontoplan | GET, PUT |
/3/articles | Artiklar/produkter | GET, POST, PUT, DELETE |
/3/orders | Orderläggning | GET, POST, PUT |
/3/offers | Offerter | GET, POST |
/3/financialyears | Räkenskapsår | GET |
/3/employees | Anställda | GET, 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 sparsamt dokumenterad hos Fortnox. MetaInformation i svaret innehåller @TotalResources, @TotalPages och @CurrentPage — använd dem för att iterera. Max limit är 500. En lägre siffra, runt 100, ger mindre svar och gör felsökningen enklare utan att kosta särskilt många extra anrop.
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 täcker färre händelsetyper än vad man är van vid från renodlade betaltjänster. För kritiska flöden — bokföring av inbetalningar, till exempel — är det klokt att komplettera med ett pollingjobb som failsafe i stället för att lita på att varje webhook kommer fram.
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:
| Status | Betydelse | Åtgärd |
|---|---|---|
| 401 | Access_token utgången eller ogiltig | Kör refresh_token-flow |
| 403 | Saknar scope | Re-auktorisera med rätt scope |
| 404 | Resurs finns inte | Kolla DocumentNumber/CustomerNumber |
| 429 | Rate limit | Backa av, vänta 5 sek |
| 400 | Valideringsfel | Läs ErrorInformation.message i svaret |
| 500 | Fortnox-fel | Retry 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")
Ett vanligt 400-fel beror på att CustomerNumber skickas som heltal i stället för sträng. Fortnox är strikt med typer — alla ID-fält ska vara strängar, även när värdet ser ut som ett tal.
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, men de underhålls i olika takt och ligger inte alltid i fas med API:ets senaste ändringar. Att gå direkt mot REST-API:et ger färre överraskningar och gör felsökningen enklare — det är också vad exemplen i den här guiden gör.
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 är det rimliga förstavalet om du bygger en svensk bokföringsintegration och vill nå flest möjliga kunder — ekosystemet och antalet befintliga integrationer är störst. Dokumentationen har luckor och webhooks är begränsade, och saknaden av sandbox betyder att du får vara försiktig med vilken tenant du utvecklar mot. Behöver du en isolerad testmiljö är Spiris API värt att titta på i stället. Börja med OAuth2-flödet, skapa en faktura, och bygg ut därifrån.
Läs vidare
Bästa bokföringsprogram 2026 — jämförelse av 13 svenska program
Oberoende jämförelse av 13 bokföringsprogram 2026. Priser, features, API-stöd och vinnare per företagstyp. Uppdaterad juli 2026.
Fortnox vs Bokio 2026 — vilket passar dig bäst?
Fortnox eller Bokio 2026? Pris, AI-automation, API och funktioner jämfört — med klar vinnare för EF, AB, e-handel och bokföringsbyrå.
Fortnox vs Visma (Spiris) 2026 — vilket är bäst för dig?
Detaljerad jämförelse 2026: pris, API, AI och funktioner. Klar vinnare per scenario — plus vad rebranden Visma eEkonomi → Spiris betyder.