# Exsitec Data Link — Brug

**Exsitec Data Link** er en lille Windows-companion, der lader et lokalt program —
et Excel-regneark, et script eller dit eget værktøj — køre **Visma Business NXT GraphQL**
queries og mutations uden selv at håndtere Visma-login eller token. Den logger på
Business NXT for dig og stiller en **styret GraphQL-gateway på din egen maskine**
til rådighed (`localhost`): Du sender et GraphQL-dokument, Data Link kontrollerer
dine **Data Link-rettigheder** for hver virksomhed, dokumentet berører, og videresender
det til Business NXT under dit eget Visma-login. Svaret fra Business NXT kommer
uændret tilbage.

> **Læser du dette med en AI-assistent?** Denne side er den komplette
> brugsreference. Du kan indsætte den i din assistent eller give den den rå Markdown på
> **`/docs/usage.md?lang=da`** (samme indhold, uden styling). Bed den skrive
> forespørgslerne i det sprog, du vælger — alt, hvad den behøver (endpoints,
> headere, rettighedsregler, request- og response-formater samt fejlformatet), står
> nedenfor. Til selve Business NXT-skemaet (tabeller, felter, filtre) skal du pege
> den mod Vismas Business NXT GraphQL-dokumentation eller bede den køre en
> introspection query gennem gatewayen.

---

## 1. Kom hurtigt i gang

1. **Installér og log på.** Installér Data Link, højreklik derefter på dens
   **bakkeikon** (nederst til højre, ved uret) → **Sign in**. Din browser åbner
   Visma Connect; log på dér.
2. **Find port og nøgle.** Læs
   `%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json` — den indeholder loopback-`port`
   og en `key`, der gælder for den aktuelle opstart.
3. **Sig hej.** `GET http://127.0.0.1:{port}/v1/status` med headeren
   `X-DataLink-Key: {key}`. Du får versionen og om du er logget på.
4. **Vælg en virksomhed.** `GET /v1/companies` viser de virksomheder, du har adgang
   til, samt hvad din Data Link-adgang og dine tilladelser lader dig gøre i hver af dem.
5. **Kør GraphQL.** `POST /v1/nxt/graphql` med en standard GraphQL-body:

   ```json
   { "query": "{ useCompany(no: 4926238) { generalLedgerAccount(first: 5) { items { accountNo name } } } }" }
   ```

   Svaret er Business NXTs eget `{ "data": …, "errors": … }`.

---

## 2. Sådan kører den

- Data Link installeres **pr. bruger** og starter automatisk, når du logger på
  Windows. Den kører stille i baggrunden og viser et lille **ikon i systembakken**
  (nederst til højre på proceslinjen, ved uret).
- **Højreklik på bakkeikonet** for at:
  - se om du er **Signed in** eller **Signed out**,
  - **Sign in** / **Sign out** (login åbner din browser til Visma Connect),
  - **Open logs folder**, **Check for updates…**, åbne denne **brugsdokumentation**
    og **Quit**.
- Du skal være **logget på**, før en GraphQL-request kan lykkes. Login er
  interaktivt (et browservindue); derefter fornyes sessionen automatisk og overlever
  genstart af Data Link.
- Companionen har **ingen "aktuel virksomhed".** Hvert GraphQL-dokument navngiver
  de virksomheder, det arbejder på (`useCompany(no: …)`), så én kørende companion
  betjener dem alle.
- **Opdateringer:** bakke → **Check for updates…** henter og installerer en ny
  version i ét trin og genstarter Data Link (en ballon bekræfter den nye version).
  En genstart giver ny port og nøgle — se *genstartssignalet* i
  [afsnit 3](#svarheadere).

### Hvor ting ligger

| Hvad | Hvor |
|------|------|
| Discovery-fil (port + nøgle) | `%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json` — kun læsbar for dig. |
| Logs | `%LOCALAPPDATA%\Exsitec\DataLink\logs\` (bakke → **Open logs folder**). Logs indeholder aldrig GraphQL-dokumenter, beløb, nøgler eller tokens. |
| Login-session | `session.dat` i samme mappe, krypteret til din Windows-bruger. |
| Selve appen | `%LOCALAPPDATA%\Exsitec.DataLink\` (brugerinstallation, der opdaterer sig selv). |

---

## 3. Kommunikation med det lokale API

### Discovery — find port og nøgle

Companionen vælger en ledig loopback-port ved opstart og skriver en lille
**discovery-fil**, så betroede lokale kaldere kan finde den:

```
%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json
```

```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"
}
```

- **`port`** — loopback-porten. APIets base URL er `http://127.0.0.1:{port}`.
- **`key`** — den delte hemmelighed for den aktuelle opstart. Send den med
  **hver** request (nedenfor). Den ændres, hver gang companionen genstarter, så læs
  den altid på ny — gem den aldrig. Parse filen med en JSON-parser: tegn som `+`
  kan være skrevet som `\u002B` escapes.
- **`instanceId`** — identificerer denne kørsel af companionen ("genstartssignalet";
  se nedenfor).

### Base URL og versionering

```
http://127.0.0.1:{port}/v1/…
```

Alle endpoints ligger under præfikset `/v1` (`protocol`-feltet i discovery-filen);
GraphQL-gatewayen er `/v1/nxt/graphql`. Bind til `127.0.0.1` (loopback) —
companionen lytter kun dér.

### Påkrævede request-headere

| Header | Hvornår | Værdi |
|--------|---------|-------|
| `X-DataLink-Key` | **Hver** request | `key` fra `endpoint.json`. |
| `Content-Type: application/json` | Requests med JSON-body | Send bodyen som UTF-8. |

Der er ingen virksomhedsheader: Virksomheden navngives inde i GraphQL-dokumentet.

### Svarheadere

| Header | Betydning |
|--------|-----------|
| `X-DataLink-Instance` | Companionens `instanceId` for denne kørsel. |

**Genstartssignal.** Hvis værdien af `X-DataLink-Instance` **ændres** mellem kald,
har companionen genstartet. Læs `endpoint.json` igen — både port **og** nøgle kan
være ændret.

### Timeouts

- `POST /v1/auth/login` venter på, at du fuldfører browserlogin (op til **5
  minutter**).
- `POST /v1/nxt/graphql` tager lige så lang tid, som Business NXT bruger; store
  læsninger kan tage mange sekunder. Giv din HTTP-klient en generøs timeout
  (minutter, ikke sekunder).

### Sikkerhedsbegrænsninger (derfor afvises browser-`fetch`)

Companionen håndhæver en loopback-tillidsgrænse:

- Requests med en **`Origin`**-header afvises (`403`) — derfor kan en webside med
  `fetch()` ikke nå den. Kald den fra en lokal proces (WinHTTP, `HttpClient`,
  `curl`, Python, PowerShell, en VBA `WinHttp.WinHttpRequest`), ikke browser-
  JavaScript.
- **`Host`**-headeren skal være `127.0.0.1`, `localhost` eller `::1`.
- En manglende eller forkert **nøgle** returnerer `401`.

---

## 4. Endpoint-reference

Hver request kræver `X-DataLink-Key`. Response-bodies fra selve Data Link er JSON
med feltnavne i **camelCase**; `POST /v1/nxt/graphql` returnerer Business NXTs body
uændret.

| Metode | Sti | Formål |
|--------|-----|--------|
| `GET` | [`/v1/status`](#get-v1status) | Health, version og loginstatus. |
| `POST` | [`/v1/auth/login`](#post-v1authlogin) | Interaktivt browserlogin. |
| `POST` | [`/v1/auth/logout`](#post-v1authlogout) | Afslut Business NXT-sessionen. |
| `GET` | [`/v1/companies`](#get-v1companies) | Virksomheder, loginet har adgang til, med dine rettigheder i hver. |
| `POST` | [`/v1/nxt/graphql`](#post-v1nxtgraphql-gatewayen) | Enhver Business NXT GraphQL-query eller mutation, rettighedskontrolleret. |

### `GET /v1/status`

Health og loginstatus. Den virker, mens du er logget ud.

```json
{
  "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`

Starter det interaktive browserlogin og returnerer, når det er færdigt. Ingen body.
Den åbner **altid** browseren, også når du allerede er logget på — tjek
`GET /v1/status` først, hvis du kun vil logge på ved behov.

```json
{ "authenticated": true }
```

Hvis browserforløbet ikke fuldføres inden for 5 minutter, fejler kaldet med
`503 unavailable` (`retryable: true`); et login, som Visma Connect afviser,
returnerer `401 unauthorized`.

### `POST /v1/auth/logout`

Afslutter Business NXT-sessionen. Returnerer `{ "authenticated": false }`.

### `GET /v1/companies`

De virksomheder, det aktuelle login har adgang til (Business NXTs
`availableCompanies`), hver med sin **tenant** (den Visma.net-kunde, den hører til)
og den [Data Link-adgang og de tilladelser](#data-link-adgang-og-tilladelser),
der gælder dér — så du kan slå et virksomheds**navn** op til dets **nummer** og se,
hvad du må gøre, før du forsøger.

```json
{
  "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 }
  ]
}
```

En virksomhed vist med `canRead: false` er en, dit Visma-login kan se, men som
Data Link ikke åbner for dig (se [afsnit 5](#data-link-adgang-og-tilladelser)).

### `POST /v1/nxt/graphql` — gatewayen

Kører **enhver** Business NXT GraphQL-query eller mutation. Bodyen er standard
GraphQL-over-HTTP:

```json
{
  "query": "query Accounts($company: Int!) { useCompany(no: $company) { generalLedgerAccount(first: 5) { items { accountNo name } } } }",
  "variables": { "company": 4926238 },
  "operationName": "Accounts"
}
```

| Felt | Påkrævet | Betydning |
|------|----------|-----------|
| `query` | ja | GraphQL-dokumentet (streng). |
| `variables` | nej | Et JSON-objekt med variabelværdier. |
| `operationName` | nej | Hvilken operation der skal køres, når dokumentet har flere. |
| `extensions` | nej | Standard GraphQL-udvidelsesdata, videresendt uændret. Rettighedskontrollen læser dem ikke. |

Det sker der:

1. Data Link **parser** dokumentet og finder ud af, hvilke virksomheder det læser,
   skriver eller sletter i ([afsnit 6](#sadan-laeser-rettighedskontrollen-et-dokument)).
2. Den **kontrollerer din Data Link-adgang og dine tilladelser** for hver af dem.
   Hvis noget ikke er tilladt, **afvises hele dokumentet**, og intet når Business NXT.
3. Ellers **videresender den din request-body uændret** til Business NXT under dit
   Visma-login og returnerer Business NXTs **HTTP-status og body ordret** — inklusiv
   Business NXTs egne `errors` og `extensions` (for eksempel
   `extensions.vbnxt-trace-id`).

```json
{
  "data": {
    "useCompany": {
      "generalLedgerAccount": {
        "items": [ { "accountNo": 1010, "name": "Varesalg DK" }, { "accountNo": 1020, "name": "Anden omsætning" } ]
      }
    }
  },
  "extensions": { "vbnxt-trace-id": "…" }
}
```

Data Link fornyer selv dit Visma-token, og når Business NXT svarer
`429 Too Many Requests`, venter den og prøver igen — begge dele er sikkert, fordi
requesten ikke blev kørt. Den prøver **aldrig** andet igen, så en mutation kan
aldrig køre to gange; en `5xx` fra Business NXT sendes tilbage til dig, som den er.

---

## 5. Data Link-adgang og tilladelser

Hvad du må gøre gennem Data Link, administreres af Exsitec i **NxtPlatform admin**.
Den gamle Config API-portal til tildelinger er væk; dokumentations- og downloadhosten
giver ikke adgang.


| Hvad | Tildelt | Tillader |
|------|---------|----------|
| **Data Link Access** | Pr. **tenant** (en Visma.net-kunde) gennem Data Link-modulet — for alle brugere eller valgte grupper, med tilsidesættelse pr. bruger. | Læsning af enhver virksomhed i den tenant (`canRead`). Uden den afvises **alt** på tenantens virksomheder, også læsninger. |
| **Write**-tilladelse | Gennem NxtPlatform-roller. | Mutations, der opretter, opdaterer, processerer eller rapporterer (`*_create`, `*_update`, `*_processings`, `*_reports`, edit-cache-styring) — på tenants, hvor du har Data Link Access (`canWrite`). |
| **Delete**-tilladelse | Gennem NxtPlatform-roller. | Mutations, der sletter (`*_delete`) — på tenants, hvor du har Data Link Access (`canDelete`). Sletning kræver Delete, ikke Write. |

Data Link **begrænser** kun det, din egen Visma-bruger må gøre — den giver aldrig
adgang, som Visma ikke ville give. En virksomhed uden for dine `availableCompanies`
afvises, selv hvis du har adgang til dens tenant.

Når du ikke har Write-tilladelse noget sted, beder Data Link også Visma om et
**skrivebeskyttet** login, næste gang du logger på, så Business NXT selv også
afviser mutations.

Din adgang og dine tilladelser hentes fra NxtPlatform, når du logger på, opdateres
med få minutters mellemrum og holdes kun i hukommelsen. Hvis NxtPlatform ikke kan
nås, gælder det seneste svar i en kort grace-periode; derefter — eller hvis Data
Link aldrig har nået NxtPlatform, siden den startede — afviser gatewayen med
`503 unavailable` (`retryable: true`) i stedet for at køre uden kontrol.

---
## 6. Sådan læser rettighedskontrollen et dokument

Kontrollen arbejder på det **parsede** dokument, aldrig på teksten, så aliases,
fragments og variables kan ikke skjule noget. Den er bevidst streng:

### Navngiv virksomheden: `useCompany(no: {companyNo})`

Hver virksomhedsspecifik læsning eller skrivning ligger under en root
`useCompany(no: …)`. `no` er virksomhedsnummeret fra `GET /v1/companies` — en
literal, en variable eller en variable-default:

```graphql
query Projects($company: Int!) {
  useCompany(no: $company) {
    orgUnit2(filter: { orgUnit2No: { _eq: 2130 } }) { items { orgUnit2No name } }
  }
}
```

(Business NXTs organisatoriske enheder navngives pr. virksomhed; i *Demo - Junge
Byg Aps 9999* er projekter `orgUnit2`.)

- Et dokument må navngive **flere virksomheder**, normalt med aliases
  (`demo: useCompany(no: 4926238) { … } junge: useCompany(no: 6307615) { … }`).
  Hver kontrolleres; én afvisning afviser hele dokumentet.
- `no` skal kunne opløses til et heltal. En manglende `no`, en `null`, en streng
  eller en udefineret variable afvises med `400 invalid_request`.
- **Hver operation** i dokumentet kontrolleres, ikke kun den som `operationName`
  vælger.
- **Fragments** og inline fragments udvides; `@skip` og `@include` ignoreres, så
  en betinget selection tæller stadig med.

### Hvad hver del af et dokument kræver

| I dokumentet | Kræver |
|--------------|--------|
| `query { useCompany(no: N) { … } }` | Read på virksomhed Ns tenant. |
| `mutation { useCompany(no: N) { x_create / x_update / x_processings / x_reports / finishEditCache … } }` | Write på Ns tenant. |
| `mutation { useCompany(no: N) { x_delete … } }` | Delete på Ns tenant. |
| `query { useCustomer(no: T) { … } }` | Read på tenant T. |
| `mutation { useCustomer(…) }` | **Afvises altid** — ændringer på kundeniveau (brugere, virksomheder) sker ikke gennem Data Link. |
| `mutation { asyncQuery(query: "…", args: {…}) }` | Det, det indlejrede dokument kræver, kontrolleret efter samme regler. |
| `availableCompanies`, `availableCustomers`, `useModel`, `api`, `asyncRequests`, `asyncResult`, `asyncQuery_delete`, introspection (`__schema`, `__type`) | Kun login. |
| Ethvert andet root-felt eller en `subscription` | Afvises (`403 forbidden`). |

### Sikker skrivning

Business NXT har ingen transaktioner på tværs af tabeller. To vaner gør skrivning
forudsigelig:

- Sæt `onError: FAIL_TABLE` på hver tabeloperation, så en ugyldig række afviser
  hele tabeloperationen i stedet for at skrive en del af den.
- For at få flere tabeloperationer til at lykkes eller fejle samlet skal du bruge
  Business NXTs **edit cache**: Kør dem med `useCompany(no: N, persistEditCache: true,
  partialRequestId: "…")` og afslut med `finishEditCache(action: SAVE)` (eller
  `DISCARD`). Hver request rettighedskontrolleres for sig selv, ligesom enhver
  anden mutation.

---

## 7. Fejl

Der er to slags fejl, og begge bruger GraphQLs error-shape.

**Business NXTs egne fejl** — en GraphQL-fejl, en værdi Business NXT afviser, en
rate limit — kommer tilbage **præcis som Business NXT sendte dem**: HTTP-status og
bodyen `{ "data": …, "errors": [ … ] }`.

**Afvisninger fra selve Data Link** (requesten nåede aldrig Business NXT) har
`extensions.code`:

```json
{
  "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 | Betydning / hvad du skal gøre |
|--------|------|-------------------------------|
| `invalid_request` | 400 | Bodyen er ikke JSON, mangler `query`, har andre medlemmer end `query`/`variables`/`operationName`/`extensions` eller gentagne property names (også når de kun adskiller sig i store/små bogstaver), `variables` er ikke et objekt, dokumentet kan ikke parses, eller et virksomhedsnummer kan ikke opløses. Ret og send igen. |
| `unauthorized` | 401 | Manglende/ugyldig nøgle, ikke logget på, eller Visma afviste login. Læs `endpoint.json` igen; log på. |
| `forbidden` | 403 | Din Data Link-adgang eller dine tilladelser tillader ikke dette på en virksomhed (`extensions.company` angiver den), virksomheden er ikke tilgængelig for dig, eller dokumentet bruger en afvist root (`useCustomer`-mutation, ukendt root, subscription). Også cross-origin-/host-blokeringer. |
| `unavailable` | 503 | Dine rettigheder kunne ikke hentes (`retryable: true`), eller Data Link er optaget. Prøv igen om lidt. |
| `internal_error` | 500 | Uventet. Tjek logmappen (bakke → **Open logs folder**). |

`GET /v1/status`, `/v1/auth/*` og `GET /v1/companies` rapporterer deres fejl i
Data Links almindelige envelope i stedet:

```json
{ "code": "unauthorized", "message": "…", "retryable": false, "instanceId": "3f2a…" }
```

En ukendt sti får en ren `404`.

---

## 8. Forholdet til Business NXT GraphQL API

Data Link **er** Business NXTs GraphQL API, tilgået gennem din egen maskine:

- **Samme schema, samme dokumenter.** Alt, hvad du kan sende til
  `https://business.visma.net/api/graphql`, sender du til `/v1/nxt/graphql`, underlagt
  [rettighedskontrollen](#sadan-laeser-rettighedskontrollen-et-dokument). Brug
  Vismas Business NXT-dokumentation eller introspection gennem gatewayen til
  tabeller, felter og filtre.
- **Ingen tokens at håndtere.** Data Link holder dit Visma Connect-login og føjer
  det til hver videresendt request; dit program ser aldrig et token.
- **Styret.** Hvert dokument kontrolleres mod din Data Link-adgang og dine
  tilladelser, før det forlader din maskine, så Exsitec kan beslutte pr. tenant,
  hvem der må læse, skrive og slette gennem Data Link.
- **Intet omformes.** Svaret er Business NXTs, byte for byte.

---

## 9. Brug fra Excel

Download og importér de to VBA-moduler nedenfor (**Alt+F11** -> **File** ->
**Import File...**). De er standardmoduler og bruger kun late binding, så der er
ikke behov for ændringer i **Tools -> References**.

| Fil | Hvad det er |
|-----|-------------|
| [`ExsitecDataLink.bas`](/docs/vba/ExsitecDataLink.bas) | Det faste SDK: autostart, discovery, login, GraphQL, JSON, fejl og output til regneark. |
| [`ExsitecDataLinkCustom.bas`](/docs/vba/ExsitecDataLinkCustom.bas) | Din workbook-skabelon: `COMPANY_NO` og eksempelmakroer til knapper. Tilpas denne fil pr. kunde/workbook. |

### Offentligt VBA API

| API | Formål |
|-----|--------|
| `NxtQuery(gql, [vars]) As Object` | Sender en GraphQL-query eller mutation til `POST /v1/nxt/graphql` og returnerer `data`-dictionaryen. Hvis dokumentet bruger `$company`, og `vars` ikke har `company`, tilføjer SDKet `COMPANY_NO`. |
| `NxtVars(name, value, ...) As Object` | Bygger en variables-dictionary. Eksempel: `NxtVars("company", 4926238, "first", 20)`. |
| `NxtItems(data, path) As Collection` | Går gennem en dot-noteret sti som `useCompany.generalLedgerAccount` og returnerer dens `items`-collection. `items: null` bliver en tom collection. |
| `WriteRows(items, target, [columns], [includeHeader]) As Range` | Skriver rækker til Excel i én blok. Nested objects udvides ét niveau (`a.b`), ISO-datoer bliver Excel-datoer, null bliver tomt, og tekst, der ligner formler, forbliver tekst. |
| `ShowNxtError()` | Viser `Err.Description` i en brugervenlig beskedboks. Brug den i error handlers. |
| `NxtUseEndpointFile(path)` | Avanceret/test hook til en ikke-standard discovery-fil. |
| `NxtUseLanguage(languageCode)` | Avanceret/test hook, der tilsidesætter SDKets beskedsprog (`en`, `da`, `sv`, `nb`). |
| `NxtUseStartPaths(primaryExe, fallbackExe)` | Avanceret/test hook til ikke-standard stier til Data Link-eksekverbare filer. |

### Mønster for knapmakro

Læg virksomhedsspecifikke indstillinger og knapmakroer i `ExsitecDataLinkCustom.bas`:

```vb
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
```

Til mutations kalder du samme `NxtQuery`-funktion med et `mutation`-dokument. SDKet
prøver aldrig mutations igen efter portændring eller transportfejl.

### Autostart og login

SDKet læser `%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json`. Hvis filen mangler,
eller `/v1/status` ikke svarer, starter det `%LOCALAPPDATA%\Exsitec.DataLink\Exsitec Data Link.exe`
(eller `current\DesktopDataLink.Host.exe`) og venter op til 30 sekunder.

Før hvert kald tjekker det `/v1/status`. Hvis du er logget ud, fortæller Excel, at
browseren åbner, kalder `/v1/auth/login` og rejser derefter en fejl, der beder dig
køre handlingen igen, når login er fuldført. Den oprindelige GraphQL-request køres
ikke automatisk efter login.

### VBA-fejlnumre

| Konstant | Betydning |
|----------|-----------|
| `NXT_ERR_NOT_RUNNING` | Data Link er ikke installeret, kunne ikke startes eller svarede aldrig. |
| `NXT_ERR_NOT_SIGNED_IN` | Du er logget ud, annullerede login, eller login kræver, at handlingen køres igen. |
| `NXT_ERR_FORBIDDEN` | Data Link Access/Permissions forbyder requesten eller virksomheden. |
| `NXT_ERR_INVALID` | GraphQL-requesten er forkert formet eller ikke tilladt af gatewayen. |
| `NXT_ERR_NXT` | Business NXT returnerede GraphQL-fejl; `Err.Description` er den første Business NXT-meddelelse. |
| `NXT_ERR_UNAVAILABLE` | Data Link, Business NXT eller rettighedstjenesten er midlertidigt utilgængelig. |
| `NXT_ERR_OTHER` | Enhver anden response uden succes. |

Beskeder bruger Data Link-bakkens sprogindstilling (`ui-settings.json`) for engelsk,
dansk, svensk og norsk, med engelsk som standard.

---

## 10. Eksempler i andre sprog

### PowerShell

```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

Kun standardbibliotek:

```python
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. Fejlfinding

| Symptom | Sandsynlig årsag | Hvad du skal gøre |
|---------|------------------|-------------------|
| Ingen `endpoint.json` eller VBA `NXT_ERR_NOT_RUNNING` | Data Link er ikke installeret eller svarede ikke. | VBA SDKet starter den automatisk; hvis det fejler, så installér den fra dokumentations-/downloadsiden. |
| Forbindelse afvist, efter det virkede før | Data Link genstartede eller afsluttede (f.eks. efter en opdatering): ny port og nøgle. | Læs `endpoint.json` igen. |
| `401` `"A valid connector key is required."` | Manglende eller forældet nøgle. | Læs `endpoint.json` igen — nøglen ændres ved hver start. |
| `401` `"Not signed in to Business NXT…"` | Ikke logget på, eller sessionen sluttede. | Bakke → **Sign in** (eller `POST /v1/auth/login`). |
| `403` `"Cross-origin requests are not permitted."` | Kaldet kom fra browser-JavaScript. | Kald fra et lokalt program eller script i stedet. |
| `403` `"You have no Data Link Access to …"` | Du har ikke Data Link Access på den virksomheds tenant. | Bed din Exsitec-kontakt om Data Link-adgang til den tenant. |
| `403` `"You have no Data Link Write / Delete permission…"` | Dine NxtPlatform-roller mangler den tilladelse (`canWrite` / `canDelete`). | Spørg din Exsitec-kontakt; tjek `GET /v1/companies`. |
| `403` `"Company … is not available to the signed‑in user."` | Din Visma-bruger kan ikke se den virksomhed, eller nummeret er tastet forkert. | Tjek `GET /v1/companies`; log ud og ind med den rigtige Visma-bruger. |
| `400` `"…'no' argument must resolve to an integer."` | `useCompany` uden nummer eller en variable, du ikke sendte. | Send virksomhedsnummeret som en `Int` literal eller variable. |
| `503` `"Data Link rights could not be fetched…"` | NxtPlatform kan ikke nås. | Prøv igen om lidt. |
| `503` `"Sign-in timed out…"` | Browserlogin blev ikke afsluttet inden for 5 minutter. | Prøv igen og fuldfør det i browseren. |
| Excel viser `"Not Responding"` under login | `/v1/auth/login` venter på browseren. | Afslut login i browseren; kør derefter Excel-handlingen igen. |

Sidder du stadig fast? Bakke → **Open logs folder** og send den nyeste logfil til
din Exsitec-kontakt — den indeholder ingen GraphQL-dokumenter, beløb, nøgler eller
tokens.

---

## 12. Ordliste

| Begreb | Betydning |
|--------|-----------|
| **Data Link** (companion) | Bakkeappen på din pc, der logger på Business NXT og betjener den lokale gateway. |
| **NXT Gateway** | `POST /v1/nxt/graphql`: Business NXT GraphQL, rettighedskontrolleret og videresendt under dit login. |
| **Rettighedskontrol** | Gatewayens kontrol af et parset GraphQL-dokument mod din Data Link Access og dine Permissions, alt eller intet. |
| **Data Link Access** | Din ret til overhovedet at bruge Data Link på en tenants virksomheder (`canRead`), tildelt i NxtPlatform admins Data Link-modul. |
| **Data Link Permission** | Write eller Delete (`canWrite` / `canDelete`), tildelt gennem NxtPlatform-roller; den gælder kun på tenants, hvor du har Data Link Access. |
| **Tenant** | En Visma.net-kunde; Data Link Access tildeles pr. tenant. |
| **Virksomhed** | En Business NXT-virksomhed, identificeret ved sit nummer og navngivet i hvert dokument med `useCompany(no: …)`. |
| **Discovery-fil** | `endpoint.json`: hvor den kørende Data Link offentliggør sin port, nøgle og instans-id. |
| **Nøgle** | Hemmeligheden pr. opstart, som hver request skal have med; den ændres ved hver start. |
| **Instans-id** | Identificerer én kørsel af Data Link; en ny værdi betyder, at den genstartede. |

---

*Dette dokument beskriver det lokale `v1`-API. Companionen rapporterer sin kørende
version i `GET /v1/status` og i bakkemenuen.*
