Exsitec Data Link

Raw ↓ — Raw Markdown for AI assistants

Exsitec Data Link — Usage

The Exsitec Data Link is a small Windows companion that lets a local program — an Excel workbook, a script, or your own tool — run Visma Business NXT GraphQL queries and mutations without handling any Visma sign‑in or token. It signs in to Business NXT for you and exposes a governed GraphQL gateway on your own machine (localhost): you send a GraphQL document, the Data Link checks your Data Link Rights for every company the document touches, and forwards it to Business NXT under your own Visma sign‑in. Business NXT's answer comes back unchanged.

Reading this with an AI assistant? This page is the complete usage reference. You can paste it into your assistant, or hand it the raw Markdown at /docs/usage.md (same content, unstyled). Ask it to write the requests in your language of choice — everything it needs (endpoints, headers, the rights rules, request and response shapes, and the error format) is below. For the Business NXT schema itself (tables, fields, filters), point it at Visma's Business NXT GraphQL documentation or ask it to run an introspection query through the gateway.


1. Quick start

  1. Install and sign in. Install the Data Link, then right‑click its tray icon (bottom‑right, near the clock) → Sign in. Your browser opens Visma Connect; sign in there.

  2. Find the port and key. Read %LOCALAPPDATA%\Exsitec\DataLink\endpoint.json — it holds the loopback port and a per‑startup key.

  3. Say hello. GET http://127.0.0.1:{port}/v1/status with the header X-DataLink-Key: {key}. You get the version and whether you are signed in.

  4. Pick a company. GET /v1/companies lists the companies you can access, with what your Data Link access and permissions let you do in each.

  5. Run GraphQL. POST /v1/nxt/graphql with a standard GraphQL body:

    { "query": "{ useCompany(no: 4926238) { generalLedgerAccount(first: 5) { items { accountNo name } } } }" }
    

    The response is Business NXT's own { "data": …, "errors": … }.


2. How it runs

Where things live

What Where
Discovery file (port + key) %LOCALAPPDATA%\Exsitec\DataLink\endpoint.json — readable only by you.
Logs %LOCALAPPDATA%\Exsitec\DataLink\logs\ (tray → Open logs folder). Logs never contain GraphQL documents, amounts, keys, or tokens.
Sign‑in session session.dat in the same folder, encrypted for your Windows user.
The app itself %LOCALAPPDATA%\Exsitec.DataLink\ (per‑user install that updates itself).

3. Talking to the local API

Discovery — find the port and key

The companion picks a free loopback port at startup and writes a small discovery file so trusted local callers can find it:

%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json
{
  "port": 51234,
  "key": "b3d1…a9f0",
  "instanceId": "3f2a…",
  "processId": 18244,
  "protocol": "v1",
  "version": "2.0.0",
  "startedUtc": "2026-10-07T07:15:00.0000000+00:00"
}

Base URL and versioning

http://127.0.0.1:{port}/v1/…

All endpoints live under the /v1 prefix (the protocol field in the discovery file); the GraphQL gateway is /v1/nxt/graphql. Bind to 127.0.0.1 (loopback) — the companion listens only there.

Required request headers

Header When Value
X-DataLink-Key Every request The key from endpoint.json.
Content-Type: application/json Requests with a JSON body Send the body as UTF‑8.

There is no company header: the company is named inside the GraphQL document.

Response headers

Header Meaning
X-DataLink-Instance The companion's instanceId for this run.

Restart signal. If the X-DataLink-Instance value changes between calls, the companion restarted. Re‑read endpoint.json — the port and key may have changed.

Timeouts

Security constraints (why a browser fetch is rejected)

The companion enforces a loopback trust boundary:


4. Endpoint reference

Every request needs X-DataLink-Key. Response bodies from the Data Link itself are JSON with camelCase field names; POST /v1/nxt/graphql returns Business NXT's body unchanged.

Method Path Purpose
GET /v1/status Health, version, and sign‑in state.
POST /v1/auth/login Interactive browser sign‑in.
POST /v1/auth/logout End the Business NXT session.
GET /v1/companies Companies the sign‑in can access, with your rights in each.
POST /v1/nxt/graphql Any Business NXT GraphQL query or mutation, rights‑checked.

GET /v1/status

Health and sign‑in state. It works while signed out.

{
  "status": "ok",
  "instanceId": "3f2a…",
  "protocol": "v1",
  "version": "2.0.0",
  "provider": { "id": "business-nxt", "authenticated": true },
  "startedUtc": "2026-10-07T07:15:00+00:00"
}

POST /v1/auth/login

Starts the interactive browser sign‑in and returns when it finishes. No body. It always opens the browser, even when you are already signed in — check GET /v1/status first if you only want to sign in when needed.

{ "authenticated": true }

If the browser flow is not completed within 5 minutes the call fails with 503 unavailable (retryable: true); a sign‑in Visma Connect rejects returns 401 unauthorized.

POST /v1/auth/logout

Ends the Business NXT session. Returns { "authenticated": false }.

GET /v1/companies

The companies the current sign‑in can access (Business NXT's availableCompanies), each with its tenant (the Visma.net customer it belongs to) and the Data Link access and permissions that apply there — so you can resolve a company name to its number and see what you may do before you try.

{
  "available": [
    { "id": "4926238", "name": "Exsitec BNXT Demo",         "tenantId": "4926230", "canRead": true, "canWrite": false, "canDelete": false },
    { "id": "6307615", "name": "Demo - Junge Byg Aps 9999", "tenantId": "2728812", "canRead": true, "canWrite": true,  "canDelete": false }
  ]
}

A company listed with canRead: false is one your Visma sign‑in can see but the Data Link does not open for you (see section 5).

POST /v1/nxt/graphql — the gateway

Runs any Business NXT GraphQL query or mutation. The body is standard GraphQL‑over‑HTTP:

{
  "query": "query Accounts($company: Int!) { useCompany(no: $company) { generalLedgerAccount(first: 5) { items { accountNo name } } } }",
  "variables": { "company": 4926238 },
  "operationName": "Accounts"
}
Field Required Meaning
query yes The GraphQL document (string).
variables no A JSON object of variable values.
operationName no Which operation to run when the document has several.
extensions no Standard GraphQL extension data, forwarded unchanged. The rights check does not read it.

What happens:

  1. The Data Link parses the document and works out every company it reads, writes or deletes in (section 6).
  2. It checks your Data Link access and permissions for each of them. If anything is not allowed, the whole document is refused and nothing reaches Business NXT.
  3. Otherwise it forwards your request body unchanged to Business NXT under your Visma sign‑in and returns Business NXT's HTTP status and body verbatim — including Business NXT's own errors and extensions (for example extensions.vbnxt-trace-id).
{
  "data": {
    "useCompany": {
      "generalLedgerAccount": {
        "items": [ { "accountNo": 1010, "name": "Varesalg DK" }, { "accountNo": 1020, "name": "Anden omsætning" } ]
      }
    }
  },
  "extensions": { "vbnxt-trace-id": "…" }
}

The Data Link renews your Visma token by itself and, when Business NXT answers 429 Too Many Requests, waits and retries — both are safe because the request did not run. It never retries anything else, so a mutation can never run twice; a 5xx from Business NXT is passed back to you as is.


What you may do through the Data Link is administered by Exsitec in NxtPlatform admin. The old Config API grants portal is gone; the docs/download host does not grant access.

What Granted Allows
Data Link Access Per tenant (a Visma.net customer) through the Data Link module — for all users or chosen groups, with a per‑user override. Reading any company in that tenant (canRead). Without it, everything on the tenant's companies is refused, reads included.
Write permission Through NxtPlatform roles. Mutations that create, update, process or report (*_create, *_update, *_processings, *_reports, edit‑cache control) — on tenants where you have Data Link Access (canWrite).
Delete permission Through NxtPlatform roles. Mutations that delete (*_delete) — on tenants where you have Data Link Access (canDelete). Deleting needs Delete, not Write.

The Data Link only ever restricts what your own Visma user may do — it never grants access Visma would not. A company outside your availableCompanies is refused even with access to its tenant.

When you have no Write permission anywhere, the Data Link also asks Visma for a read‑only sign‑in the next time you sign in, so Business NXT itself refuses mutations too.

Your access and permissions are fetched from NxtPlatform when you sign in and refreshed every few minutes, and kept in memory only. If NxtPlatform cannot be reached, the last answer keeps applying for a short grace period; after that — or if the Data Link has never reached NxtPlatform since it started — the gateway refuses with 503 unavailable (retryable: true) rather than run unchecked.


6. How the Rights Check reads a document

The check works on the parsed document, never on its text, so aliases, fragments and variables cannot hide anything. It is deliberately strict:

Naming the company: useCompany(no: {companyNo})

Every company‑scoped read or write sits under a root useCompany(no: …). The no is the company number from GET /v1/companies — a literal, a variable, or a variable default:

query Projects($company: Int!) {
  useCompany(no: $company) {
    orgUnit2(filter: { orgUnit2No: { _eq: 2130 } }) { items { orgUnit2No name } }
  }
}

(Business NXT's organisational units are relabelled per company; in Demo - Junge Byg Aps 9999 projects are orgUnit2.)

What each part of a document needs

In the document Needs
query { useCompany(no: N) { … } } Read on company N's tenant.
mutation { useCompany(no: N) { x_create / x_update / x_processings / x_reports / finishEditCache … } } Write on N's tenant.
mutation { useCompany(no: N) { x_delete … } } Delete on N's tenant.
query { useCustomer(no: T) { … } } Read on tenant T.
mutation { useCustomer(…) } Always refused — customer‑level changes (users, companies) are not done through the Data Link.
mutation { asyncQuery(query: "…", args: {…}) } Whatever the embedded document needs, checked by the same rules.
availableCompanies, availableCustomers, useModel, api, asyncRequests, asyncResult, asyncQuery_delete, introspection (__schema, __type) Only a sign‑in.
Any other root field, or a subscription Refused (403 forbidden).

Writing safely

Business NXT has no cross‑table transactions. Two habits keep a write predictable:


7. Errors

There are two kinds of error, and both use GraphQL's error shape.

Business NXT's own errors — a GraphQL error, a value Business NXT rejects, a rate limit — come back exactly as Business NXT sent them: its HTTP status and its { "data": …, "errors": [ … ] } body.

Refusals by the Data Link itself (the request never reached Business NXT) carry extensions.code:

{
  "errors": [
    {
      "message": "You have no Data Link Write permission for company 'Exsitec BNXT Demo' (4926238).",
      "extensions": { "code": "forbidden", "retryable": false, "instanceId": "3f2a…", "company": "4926238" }
    }
  ]
}
code HTTP Meaning / what to do
invalid_request 400 Body is not JSON, has no query, has members other than query/variables/operationName/extensions or repeated property names (also differing only in case), variables is not an object, the document does not parse, or a company number cannot be resolved. Fix and resend.
unauthorized 401 Missing/invalid key, not signed in, or Visma rejected the sign‑in. Re‑read endpoint.json; sign in.
forbidden 403 Your Data Link access or permissions do not allow this on a company (extensions.company names it), the company is not available to you, or the document uses a refused root (useCustomer mutation, unknown root, subscription). Also cross‑origin/host blocks.
unavailable 503 Your rights could not be fetched (retryable: true), or the Data Link is busy. Retry shortly.
internal_error 500 Unexpected. Check the logs folder (tray → Open logs folder).

GET /v1/status, /v1/auth/* and GET /v1/companies report their errors in the Data Link's plain envelope instead:

{ "code": "unauthorized", "message": "…", "retryable": false, "instanceId": "3f2a…" }

An unknown path gets a bare 404.


8. Relationship to the Business NXT GraphQL API

The Data Link is Business NXT's GraphQL API, reached through your own machine:


9. Using it from Excel

Download and import the two VBA modules below (Alt+F11 -> File -> Import File...). They are standard modules and use only late binding, so no Tools -> References changes are needed.

File What it is
ExsitecDataLink.bas The fixed SDK: auto-start, discovery, sign-in, GraphQL, JSON, errors and worksheet output.
ExsitecDataLinkCustom.bas Your workbook template: COMPANY_NO and example button macros. Change this file per customer/workbook.

Public VBA API

API Purpose
NxtQuery(gql, [vars]) As Object Sends a GraphQL query or mutation to POST /v1/nxt/graphql and returns the data dictionary. If the document uses $company and vars has no company, the SDK adds COMPANY_NO.
NxtVars(name, value, ...) As Object Builds a variables dictionary. Example: NxtVars("company", 4926238, "first", 20).
NxtItems(data, path) As Collection Walks a dotted path such as useCompany.generalLedgerAccount and returns its items collection. items: null becomes an empty collection.
WriteRows(items, target, [columns], [includeHeader]) As Range Writes rows to Excel in one block. Nested objects expand one level (a.b), ISO dates become Excel dates, null becomes empty, and formula-looking text stays text.
ShowNxtError() Shows Err.Description in a friendly message box. Use it in error handlers.
NxtUseEndpointFile(path) Advanced/test hook for a non-default discovery file.
NxtUseLanguage(languageCode) Advanced/test hook that overrides the SDK message language (en, da, sv, nb).
NxtUseStartPaths(primaryExe, fallbackExe) Advanced/test hook for non-default Data Link executable paths.

Button macro pattern

Put company-specific settings and button macros in ExsitecDataLinkCustom.bas:

Public Const COMPANY_NO As Long = 4926238

Public Sub ListAccounts()
    On Error GoTo Failed
    Dim data As Object
    Set data = NxtQuery("query Accounts($company: Int!) { useCompany(no: $company) { generalLedgerAccount(first: 20) { items { accountNo name } } } }")
    WriteRows NxtItems(data, "useCompany.generalLedgerAccount"), ActiveSheet.Range("A1"), Array("accountNo", "name")
    Exit Sub
Failed:
    ShowNxtError
End Sub

For mutations, call the same NxtQuery function with a mutation document. The SDK never retries mutations after a port change or transport failure.

Auto-start and sign-in

The SDK reads %LOCALAPPDATA%\Exsitec\DataLink\endpoint.json. If the file is missing or /v1/status does not answer, it starts %LOCALAPPDATA%\Exsitec.DataLink\Exsitec Data Link.exe (or current\DesktopDataLink.Host.exe) and waits up to 30 seconds.

Before every call it checks /v1/status. If you are signed out, Excel tells you that the browser will open, calls /v1/auth/login, then raises an error telling you to run the action again once sign-in is complete. The original GraphQL request is not run automatically after sign-in.

VBA error numbers

Constant Meaning
NXT_ERR_NOT_RUNNING The Data Link is not installed, could not be started, or never answered.
NXT_ERR_NOT_SIGNED_IN You are signed out, canceled sign-in, or sign-in needs the action to be run again.
NXT_ERR_FORBIDDEN Data Link Access/Permissions forbid the request or company.
NXT_ERR_INVALID The GraphQL request is malformed or not allowed by the gateway.
NXT_ERR_NXT Business NXT returned GraphQL errors; Err.Description is the first Business NXT message.
NXT_ERR_UNAVAILABLE The Data Link, Business NXT or the rights service is temporarily unavailable.
NXT_ERR_OTHER Any other non-success response.

Messages use the Data Link tray language setting (ui-settings.json) for English, Danish, Swedish and Norwegian, defaulting to English.


10. Examples in other languages

PowerShell

# 1. Discover the running companion.
$ep   = Get-Content "$env:LOCALAPPDATA\Exsitec\DataLink\endpoint.json" | ConvertFrom-Json
$base = "http://127.0.0.1:$($ep.port)/v1"
$hdr  = @{ "X-DataLink-Key" = $ep.key }

# 2. Check status / sign-in.
Invoke-RestMethod "$base/status" -Headers $hdr

# 3. Resolve a company name to its number, and see your rights there.
$companies = (Invoke-RestMethod "$base/companies" -Headers $hdr).available
$company   = $companies | Where-Object { $_.name -eq "Exsitec BNXT Demo" }

# 4. Run GraphQL.
$body = @{
    query     = 'query A($c: Int!) { useCompany(no: $c) { generalLedgerAccount(first: 5) { items { accountNo name } } } }'
    variables = @{ c = [int]$company.id }
} | ConvertTo-Json -Depth 5

$result = Invoke-RestMethod "$base/nxt/graphql" -Method Post -Headers $hdr `
    -ContentType "application/json" -Body $body
$result.data.useCompany.generalLedgerAccount.items

Python

Standard library only:

import json, os, urllib.request

path = os.path.join(os.environ["LOCALAPPDATA"], "Exsitec", "DataLink", "endpoint.json")
with open(path, encoding="utf-8-sig") as f:
    ep = json.load(f)
base = f"http://127.0.0.1:{ep['port']}/v1"

def call(method, path, body=None):
    headers = {"X-DataLink-Key": ep["key"]}
    data = None
    if body is not None:
        data = json.dumps(body).encode("utf-8")
        headers["Content-Type"] = "application/json"
    request = urllib.request.Request(base + path, data=data, headers=headers, method=method)
    with urllib.request.urlopen(request, timeout=300) as response:   # HTTPError carries the GraphQL errors
        return json.load(response)

def graphql(query, **variables):
    return call("POST", "/nxt/graphql", {"query": query, "variables": variables})

companies = call("GET", "/companies")["available"]
junge = next(c for c in companies if c["name"] == "Demo - Junge Byg Aps 9999")

result = graphql(
    "query P($c: Int!, $p: Int!) { useCompany(no: $c) { orgUnit2(filter: { orgUnit2No: { _eq: $p } }) { items { orgUnit2No name } } } }",
    c=int(junge["id"]), p=2130)
print(result["data"]["useCompany"]["orgUnit2"]["items"])

11. Troubleshooting

Symptom Likely cause What to do
No endpoint.json, or VBA NXT_ERR_NOT_RUNNING The Data Link is not installed or did not answer. The VBA SDK starts it automatically; if that fails, install it from the docs/download page.
Connection refused after it worked before The Data Link restarted or quit (e.g. after an update): new port and key. Re‑read endpoint.json.
401 "A valid connector key is required." Missing or stale key. Re‑read endpoint.json — the key changes on every start.
401 "Not signed in to Business NXT…" Not signed in, or the session ended. Tray → Sign in (or POST /v1/auth/login).
403 "Cross-origin requests are not permitted." The call came from browser JavaScript. Call from a local program or script instead.
403 "You have no Data Link Access to …" You have no Data Link Access on that company's tenant. Ask your Exsitec contact for Data Link access to that tenant.
403 "You have no Data Link Write / Delete permission…" Your NxtPlatform roles lack that permission (canWrite / canDelete). Ask your Exsitec contact; check GET /v1/companies.
403 "Company … is not available to the signed‑in user." Your Visma user cannot see that company, or a typo in the number. Check GET /v1/companies; sign out and in with the right Visma user.
400 "…'no' argument must resolve to an integer." useCompany without a number, or a variable you did not send. Pass the company number as an Int literal or variable.
503 "Data Link rights could not be fetched…" NxtPlatform is unreachable. Retry shortly.
503 "Sign-in timed out…" The browser sign‑in was not finished within 5 minutes. Try again and complete it in the browser.
Excel shows "Not Responding" during sign-in /v1/auth/login waits for the browser. Finish signing in in the browser; then run the Excel action again.

Still stuck? Tray → Open logs folder and send the latest log file to your Exsitec contact — it contains no GraphQL documents, amounts, keys, or tokens.


12. Glossary

Term Meaning
Data Link (companion) The tray app on your PC that signs in to Business NXT and serves the local gateway.
NXT Gateway POST /v1/nxt/graphql: Business NXT GraphQL, rights‑checked and forwarded under your sign‑in.
Rights Check The gateway's check of a parsed GraphQL document against your Data Link Access and Permissions, all or nothing.
Data Link Access Your right to use the Data Link at all on a tenant's companies (canRead), granted in NxtPlatform admin's Data Link module.
Data Link Permission Write or Delete (canWrite / canDelete), granted through NxtPlatform roles; it applies only on tenants where you have Data Link Access.
Tenant A Visma.net customer; Data Link Access is granted per tenant.
Company A Business NXT company, identified by its number and named in each document with useCompany(no: …).
Discovery file endpoint.json: where the running Data Link publishes its port, key, and instance id.
Key The per‑startup secret every request must carry; it changes on each start.
Instance id Identifies one run of the Data Link; a new value means it restarted.

This document describes the v1 local API. The companion reports its running version in GET /v1/status and on the tray menu.

↑ Top