Integrator onboarding API

Endret Sun, 9 Aug ved 10:28 AM

Systima onboarding API for integration partners

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.

Base URLs

EnvironmentBase URL
Test (staging)https://api-stage-systima.azurewebsites.net
Productionhttps://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.

Authentication

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.


1. GET /api-external/subscription-plans

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 }] } ]
FieldDescription
codePass this value as plan when registering a company.
nameHuman-readable plan name.
prices[].periodMONTHLY or YEARLY.
prices[].pricePrice 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 smallmedium and large.


2. POST /api-external/onboarding

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"
  }'

Body

user - the person who becomes the owner of the company.

FieldRequiredNotes
firstNameyes
lastNameyes
emailyesValid address, not a disposable domain, not already registered in Systima. Login name and destination of the "set your password" e-mail.
phoneno

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

FieldRequiredNotes
organizationNumberyesNorwegian organisation number. Must not already exist in Systima.
nameyes
phoneyes
emailyesGeneral company e-mail.
billingEmailyesWhere invoices from Systima are sent. May differ from email.
typeyesCompany form: ASANSASADAENKFLIKSNUFSASBSTIUBUTLAKBOKTRFSAMIKSPRIVATPERSONANNAOTHER.
addressLine1yes
addressLine2no
postalCodeyes
cityyes
countryyes
vatPaymentFrequencyyesNONEMONTHLYBIMONTHLY or ANNUALLY.
registeredAtnoYYYY-MM-DD.
sicCodeno
accountingStartDatenoYYYY-MM-DD.

Plan selection

FieldRequiredNotes
plannocode 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.
periodnoMONTHLY (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.


3. Redirecting the user

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.

4. POST /api-external/companies/{companyId}/token

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"
}
FieldDescription
accessTokenSend as Authorization: Bearer <accessToken> on the endpoints in section 5.
expiresInLifetime in seconds.
companyIdEcho of the company the token is scoped to.

Two limits apply, and they are different things

LimitValueWhat it means
Token lifetime1 hourWhen it expires, call this endpoint again. There is no refresh token by design.
Access window24 hours from the moment the company was created, unless agreed otherwiseInside 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.


5. Posting bookkeeping

Both endpoints take the company token from section 4 as an ordinary bearer token - the provisioning headers are not used here.

GET /api-external/companies/{companyId}/accounts

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>'

POST /api-external/companies/{companyId}/vouchers

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 }
        ]
      }
    ]
  }'
FieldRequiredNotes
voucherNumbertechnically no, in practice yesYour own voucher number. It is the only protection against double bookkeeping - see below.
descriptionnoShown on the voucher.
fileIdnoAttach a previously uploaded file.
lines[].accountNumberyesAccount numbers that do not exist yet are created from the standard chart of accounts.
lines[].descriptionyes
lines[].dateyesYYYY-MM-DD. The date sits on the line, not on the voucher.
lines[].sumyesPositive debits the account, negative credits it. The lines of one voucher must add up to 0.
lines[].vatIdnoOmit 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.

Retries and duplicate protection

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:

SituationResult
You retry a batch that never got through204 - booked normally.
You retry a batch that did get through - e.g. your connection dropped after we committed204 - recognised as a repeat, nothing is booked twice.
Some numbers in the batch already exist, others do not422 same_voucher_number. The conflicting numbers come back in meta.numbers, and nothing is booked.
The same number appears twice in one request422 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.


6. Errors

Errors are returned as JSON: { "code": "...", "message": "...", "status": ... }.

StatuscodeWhen
401Missing or invalid Authorization Bearer token.
403provisioning_deniedMissing, wrong or deactivated X-Provisioning-Client-IdX-Provisioning-Key. Wrong key and unknown client id give the same answer on purpose.
403company_token_deniedThat company was not created by your credentials. Check the companyId.
403company_token_window_expiredMore than the agreed window has passed since the company was created. Contact Systima to reopen it.
422email_already_existThe e-mail is already registered in Systima.
422duplicated_entityThe organisation number already exists in Systima.
422same_voucher_numberSee section 5. Read meta.numbers for the conflicting voucher numbers.
422Validation failed, e.g. a required field is missing or plan is not a known plan code.
429Too 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.


7. Integration checklist

  1. Fetch /api-external/subscription-plans and render the plan choice (prices in øre).
  2. Collect user and company details; validate the organisation number and e-mail on your side to avoid predictable 422s.
  3. Call /api-external/onboarding server-side with the three auth headers.
  4. Redirect the browser to redirectUrl right away.
  5. 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.
  6. Give every voucher a stable voucherNumber and reuse the same number on retries.
  7. Handle 422 by showing the message to the end user, and 401/403 as a configuration error on your side - contact Systima.

8. What you do not need to do

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 /onboarding returns.
  • 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

Fortell oss hvordan vi kan forbedre denne artikkelen.

Velg minst én av grunnene

Tilbakemeldingen er sendt inn

Vi setter pris på tilbakemeldingen din og vil prøve å rette på artikkelen