This document is for developers integrating with Systima. It describes how to register a new customer - user and company in a single call - how to land that user in Systima already signed in, and how to post bookkeeping into that company from your own system.
You need credentials from Systima before you start; see Authentication.
| Environment | Base URL |
|---|---|
| Test (staging) | https://api-stage-systima.azurewebsites.net |
| Production | https://api.systima.no |
All endpoints below are relative to the base URL. Test your integration against staging first; staging credentials do not work in production.
Every request must carry three headers:
Authorization: Bearer <integrator token> X-Provisioning-Client-Id: <your client id> X-Provisioning-Key: <your provisioning key>
Systima issues all three to you. The provisioning key is a secret: keep it server-side, never ship it in a browser or mobile app, and never log it. If it leaks, contact Systima - a new key can be issued and the old one deactivated immediately.
Returns the subscription plans that can be activated for a new company. Use it to render a plan picker. Optional, but recommended over hardcoding plans, because prices change over time.
Request
curl 'https://api.systima.no/api-external/subscription-plans' \ -H 'Authorization: Bearer <integrator token>' \ -H 'X-Provisioning-Client-Id: <your client id>' \ -H 'X-Provisioning-Key: <your provisioning key>'Response 200
[ { "code": "small", "name": "Invoicing", "prices": [{ "period": "MONTHLY", "price": 0 }, { "period": "YEARLY", "price": 0 }] }, { "code": "medium", "name": "Invoicing and accounting", "prices": [{ "period": "MONTHLY", "price": 19900 }, { "period": "YEARLY", "price": 214800 }] }, { "code": "large", "name": "Invoicing, accounting and salary", "prices": [{ "period": "MONTHLY", "price": 24900 }, { "period": "YEARLY", "price": 268800 }] } ]| Field | Description |
|---|---|
code | Pass this value as plan when registering a company. |
name | Human-readable plan name. |
prices[].period | MONTHLY or YEARLY. |
prices[].price | Price in øre for that period - 19900 = 199 NOK per month, 214800 = 2 148 NOK per year. |
A company has exactly one plan at a time. The response may also contain medium_sb and salary; a plan picker should normally show only small, medium and large.
Creates the user and the company, optionally activates a subscription plan, and returns a one-time URL that logs the user in.
Request
curl -X POST 'https://api.systima.no/api-external/onboarding' \ -H 'Authorization: Bearer <integrator token>' \ -H 'X-Provisioning-Client-Id: <your client id>' \ -H 'X-Provisioning-Key: <your provisioning key>' \ -H 'Content-Type: application/json' \ -d '{
"user": {
"firstName": "Edvard",
"lastName": "Munch",
"email": "edvard@example.no",
"phone": "+4721422121"
},
"company": {
"organizationNumber": "999999999",
"name": "Operahuset AS",
"phone": "+4721422121",
"email": "post@example.no",
"billingEmail": "faktura@example.no",
"type": "AS",
"addressLine1": "Kirsten Flagstads Plass 1",
"addressLine2": null,
"postalCode": "0150",
"city": "Oslo",
"country": "Norway",
"vatPaymentFrequency": "BIMONTHLY",
"registeredAt": "1996-10-15",
"sicCode": "12.99",
"accountingStartDate": "2026-01-01"
},
"plan": "medium",
"period": "MONTHLY"
}'user - the person who becomes the owner of the company.
| Field | Required | Notes |
|---|---|---|
firstName | yes | |
lastName | yes | |
email | yes | Valid address, not a disposable domain, not already registered in Systima. Login name and destination of the "set your password" e-mail. |
phone | no |
There is no password field. Systima creates the account with a random password and e-mails the user a link to set their own.
company
| Field | Required | Notes |
|---|---|---|
organizationNumber | yes | Norwegian organisation number. Must not already exist in Systima. |
name | yes | |
phone | yes | |
email | yes | General company e-mail. |
billingEmail | yes | Where invoices from Systima are sent. May differ from email. |
type | yes | Company form: AS, ANS, ASA, DA, ENK, FLI, KS, NUF, SA, SB, STI, UB, UTLA, KBO, KTRF, SAM, IKS, PRIVATPERSON, ANNA, OTHER. |
addressLine1 | yes | |
addressLine2 | no | |
postalCode | yes | |
city | yes | |
country | yes | |
vatPaymentFrequency | yes | NONE, MONTHLY, BIMONTHLY or ANNUALLY. |
registeredAt | no | YYYY-MM-DD. |
sicCode | no | |
accountingStartDate | no | YYYY-MM-DD. |
Plan selection
| Field | Required | Notes |
|---|---|---|
plan | no | A code from the plan catalog. When set, the company is created with that subscription already active and the user goes straight to work. When omitted, the user is asked to choose a plan on first login. |
period | no | MONTHLY (default) or YEARLY. Only used together with plan. |
Response 200
{ "userId": "6d91d632-2174-4717-97fb-0e3bc11c620d", "companyId": "fd036958-0872-4dba-b777-0a52442e3131", "redirectUrl": "https://app.systima.no/integrator-login#ticket=4ca77872fbe74fe3aa3860c5df3e543a"
}Store userId and companyId if you need to reference the customer later.
Send the user to redirectUrl immediately after the call - a browser redirect, not a link saved for later.
- The ticket in the URL fragment (
#ticket=...) is single-use and expires after 60 seconds. - Systima's web app exchanges it for a real session; the user lands inside their new company, signed in, with no password step.
- Never store, log, e-mail or forward the ticket. If it expires, the user can still sign in normally via the "set your password" e-mail.
Not released yet. Sections 1-3 are live in production today; this endpoint and section 5 ship with SYS-9961. The contract below is final - you can build against it.
Returns a short-lived token for a company you created, so your system can work in that company through the external API. It uses the same three headers as everything else: you never ask the customer for their Systima password, and they do not have to grant you anything.
Request
curl -X POST 'https://api.systima.no/api-external/companies/<companyId>/token' \ -H 'Authorization: Bearer <integrator token>' \ -H 'X-Provisioning-Client-Id: <your client id>' \ -H 'X-Provisioning-Key: <your provisioning key>'companyId is the value returned by /onboarding.
Response 200
{ "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expiresIn": 3600, "companyId": "fd036958-0872-4dba-b777-0a52442e3131"
}| Field | Description |
|---|---|
accessToken | Send as Authorization: Bearer <accessToken> on the endpoints in section 5. |
expiresIn | Lifetime in seconds. |
companyId | Echo of the company the token is scoped to. |
| Limit | Value | What it means |
|---|---|---|
| Token lifetime | 1 hour | When it expires, call this endpoint again. There is no refresh token by design. |
| Access window | 24 hours from the moment the company was created, unless agreed otherwise | Inside the window you may request as many tokens as you need. Once it closes, this endpoint returns 403 company_token_window_expired and stays closed. |
Requesting a new token is a normal part of operation, not an error path - build it into your client rather than caching one token for a long job. The access window is what makes retries and downtime survivable: if your server is down for several hours, request a fresh token when it comes back.
The window length is a setting on our side, per partner. Tell Systima if 24 hours does not fit how your system actually runs - changing it does not require a release.
A token only works for a company created by your credentials. Anything else returns 403 company_token_denied. Every voucher posted with it is traceable back to your integration.
Both endpoints take the company token from section 4 as an ordinary bearer token - the provisioning headers are not used here.
Returns the company's chart of accounts. Use it to verify an account exists before posting to it.
curl 'https://api.systima.no/api-external/companies/<companyId>/accounts' \ -H 'Authorization: Bearer <accessToken>'Posts one or more vouchers. Returns 204 No Content on success.
curl -X POST 'https://api.systima.no/api-external/companies/<companyId>/vouchers' \ -H 'Authorization: Bearer <accessToken>' \ -H 'Content-Type: application/json' \ -d '{
"vouchers": [
{
"voucherNumber": "FF-2026-0001",
"description": "Salgsfaktura 1001",
"lines": [
{ "accountNumber": "1920", "description": "Innbetaling", "date": "2026-08-04", "sum": 20000 },
{ "accountNumber": "3000", "description": "Salg", "date": "2026-08-04", "sum": -20000 }
]
}
]
}'| Field | Required | Notes |
|---|---|---|
voucherNumber | technically no, in practice yes | Your own voucher number. It is the only protection against double bookkeeping - see below. |
description | no | Shown on the voucher. |
fileId | no | Attach a previously uploaded file. |
lines[].accountNumber | yes | Account numbers that do not exist yet are created from the standard chart of accounts. |
lines[].description | yes | |
lines[].date | yes | YYYY-MM-DD. The date sits on the line, not on the voucher. |
lines[].sum | yes | Positive debits the account, negative credits it. The lines of one voucher must add up to 0. |
lines[].vatId | no | Omit it and VAT is derived from the account's standard VAT code. Set it to override. |
The whole batch is one database transaction: if one voucher fails, none of them are booked.
Always send a stable voucherNumber and keep it identical when you retry. Never reuse a number for different content. With that in place, retrying is safe:
| Situation | Result |
|---|---|
| You retry a batch that never got through | 204 - booked normally. |
| You retry a batch that did get through - e.g. your connection dropped after we committed | 204 - recognised as a repeat, nothing is booked twice. |
| Some numbers in the batch already exist, others do not | 422 same_voucher_number. The conflicting numbers come back in meta.numbers, and nothing is booked. |
| The same number appears twice in one request | 422 same_voucher_number. |
So a retry either succeeds or tells you exactly which numbers collided - you never have to treat an error as success. Without a voucherNumber there is no duplicate protection at all, and a retry will book the same voucher twice.
Errors are returned as JSON: { "code": "...", "message": "...", "status": ... }.
| Status | code | When |
|---|---|---|
| 401 | Missing or invalid Authorization Bearer token. | |
| 403 | provisioning_denied | Missing, wrong or deactivated X-Provisioning-Client-Id / X-Provisioning-Key. Wrong key and unknown client id give the same answer on purpose. |
| 403 | company_token_denied | That company was not created by your credentials. Check the companyId. |
| 403 | company_token_window_expired | More than the agreed window has passed since the company was created. Contact Systima to reopen it. |
| 422 | email_already_exist | The e-mail is already registered in Systima. |
| 422 | duplicated_entity | The organisation number already exists in Systima. |
| 422 | same_voucher_number | See section 5. Read meta.numbers for the conflicting voucher numbers. |
| 422 | Validation failed, e.g. a required field is missing or plan is not a known plan code. | |
| 429 | Too many requests. /onboarding is rate limited to 100 requests per 15 minutes per calling IP address. Retry with backoff. |
Match on code, not on message - messages are meant for humans and may change.
The whole registration runs in a single database transaction: if the call does not return 200, nothing was created - no user, no company, no subscription. It is safe to fix the payload and retry.
- Fetch
/api-external/subscription-plansand render the plan choice (prices in øre). - Collect user and company details; validate the organisation number and e-mail on your side to avoid predictable 422s.
- Call
/api-external/onboardingserver-side with the three auth headers. - Redirect the browser to
redirectUrlright away. - Store
companyId. When you need to post bookkeeping, request a company token and refresh it whenever it expires - do not cache one token for a long-running job. - Give every voucher a stable
voucherNumberand reuse the same number on retries. - Handle 422 by showing the message to the end user, and 401/403 as a configuration error on your side - contact Systima.
A common assumption is that the partner has to create the company for themselves, do the bookkeeping, then hand ownership to the customer and withdraw. None of that applies here:
- You do not request access to the company. Your provisioning credentials plus the company token from section 4 are the access.
- You do not invite the customer as owner. They already are, from the moment
/onboardingreturns. - You do not remove yourself afterwards. The access window closes on its own, and Systima can revoke your integration at any time.
- You never ask the customer for their Systima password. If a flow seems to require it, tell Systima - it means an endpoint is missing and we would rather build it.
Var denne artikkelen nyttig?
Så bra!
Takk for din tilbakemelding
Beklager at vi ikke kunne være mer til hjelp
Takk for din tilbakemelding
Tilbakemeldingen er sendt inn
Vi setter pris på tilbakemeldingen din og vil prøve å rette på artikkelen