# Exsitec Data Link — bruk

**Exsitec Data Link** er en liten Windows-companion som lar et lokalt program —
en Excel-arbeidsbok, et skript eller ditt eget verktøy — kjøre **Visma Business NXT GraphQL**-
spørringer og mutasjoner uten å håndtere Visma-pålogging eller token selv. Den logger deg på
Business NXT og eksponerer en **styrt GraphQL-gateway på din egen maskin**
(`localhost`): du sender et GraphQL-dokument, Data Link kontrollerer **Data Link-
rettighetene** dine for hvert selskap dokumentet berører, og videresender det til Business NXT
under din egen Visma-pålogging. Svaret fra Business NXT kommer uendret tilbake.

> **Leser du dette med en KI-assistent?** Denne siden er den komplette
> bruksreferansen. Du kan lime den inn i assistenten, eller gi den rå Markdown på
> **`/docs/usage.md?lang=nb`** (samme innhold, uten styling). Be den skrive forespørslene i
> språket du foretrekker — alt den trenger (endepunkter, headere, rettighetsregler,
> request- og response-formater og feilformatet) finnes nedenfor. For selve
> Business NXT-skjemaet (tabeller, felter, filtre) kan du vise den til Vismas
> Business NXT GraphQL-dokumentasjon eller be den kjøre en introspection-query
> gjennom gatewayen.

---

## 1. Kom raskt i gang

1. **Installer og logg på.** Installer Data Link, høyreklikk deretter på **tray-ikonet**
   (nede til høyre, ved klokken) → **Sign in**. Nettleseren åpner Visma Connect;
   logg på der.
2. **Finn porten og nøkkelen.** Les
   `%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json` — den inneholder loopback-`port`
   og en `key` per oppstart.
3. **Si hei.** `GET http://127.0.0.1:{port}/v1/status` med headeren
   `X-DataLink-Key: {key}`. Du får versjonen og om du er logget på.
4. **Velg et selskap.** `GET /v1/companies` lister selskapene du har tilgang til,
   med hva Data Link-tilgangen og tillatelsene dine lar deg gjøre i hvert av dem.
5. **Kjø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 egen `{ "data": …, "errors": … }`.

---

## 2. Slik kjører den

- Data Link installeres **per bruker** og starter automatisk når du logger på
  Windows. Den kjører stille i bakgrunnen og viser et lite **system-tray-ikon**
  (nede til høyre på oppgavelinjen, ved klokken).
- **Høyreklikk tray-ikonet** for å:
  - se om du er **Signed in** eller **Signed out**,
  - **Sign in** / **Sign out** (pålogging åpner nettleseren til Visma Connect),
  - **Open logs folder**, **Check for updates…**, åpne denne **bruksdokumentasjonen**,
    og **Quit**.
- Du må være **logget på** før en GraphQL-forespørsel kan lykkes. Pålogging er
  interaktiv (et nettleservindu); etterpå fornyes sesjonen automatisk og overlever
  omstarter av Data Link.
- Companionen har **ikke noe «gjeldende selskap».** Hvert GraphQL-dokument navngir
  selskapene det arbeider med (`useCompany(no: …)`), så én kjørende companion betjener
  dem alle.
- **Oppdateringer:** tray → **Check for updates…** laster ned og installerer en ny versjon i
  ett trinn og starter Data Link på nytt (en varsling bekrefter den nye versjonen). En
  omstart gir ny port og nøkkel — se *omstartssignalet* i
  [seksjon 3](#svarheadere).

### Hvor ting ligger

| Hva | Hvor |
|-----|------|
| Oppdagelsesfil (port + nøkkel) | `%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json` — kan bare leses av deg. |
| Logger | `%LOCALAPPDATA%\Exsitec\DataLink\logs\` (tray → **Open logs folder**). Logger inneholder aldri GraphQL-dokumenter, beløp, nøkler eller token. |
| Påloggingssesjon | `session.dat` i samme mappe, kryptert for Windows-brukeren din. |
| Selve appen | `%LOCALAPPDATA%\Exsitec.DataLink\` (per-bruker-installasjon som oppdaterer seg selv). |

---

## 3. Snakke med det lokale API-et

### Oppdagelse — finn porten og nøkkelen

Companionen velger en ledig loopback-port ved oppstart og skriver en liten **oppdagelsesfil**
slik at betrodde lokale kallere kan finne 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. API-base-URL-en er `http://127.0.0.1:{port}`.
- **`key`** — den delte hemmeligheten per oppstart. Send den på **hver** forespørsel (nedenfor).
  Den endres hver gang companionen starter på nytt, så les den på nytt — aldri lagre den.
  Tolk filen med en JSON-parser: tegn som `+` kan være skrevet som
  `\u002B` escapes.
- **`instanceId`** — identifiserer denne kjøringen av companionen («omstartssignalet»;
  se nedenfor).

### Base-URL og versjonering

```
http://127.0.0.1:{port}/v1/…
```

Alle endepunkter ligger under prefikset `/v1` (`protocol`-feltet i oppdagelsesfilen);
GraphQL-gatewayen er `/v1/nxt/graphql`. Koble til `127.0.0.1` (loopback) —
companionen lytter bare der.

### Obligatoriske request-headere

| Header | Når | Verdi |
|--------|-----|-------|
| `X-DataLink-Key` | **Hver** forespørsel | `key` fra `endpoint.json`. |
| `Content-Type: application/json` | Forespørsler med JSON-body | Send body som UTF-8. |

Det finnes ingen selskapsheader: selskapet navngis inne i GraphQL-dokumentet.

### Svarheadere

| Header | Betydning |
|--------|-----------|
| `X-DataLink-Instance` | Companionens `instanceId` for denne kjøringen. |

**Omstartssignal.** Hvis verdien av `X-DataLink-Instance` **endres** mellom kall,
startet companionen på nytt. Les `endpoint.json` på nytt — både porten **og** nøkkelen kan ha
endret seg.

### Tidsavbrudd

- `POST /v1/auth/login` venter på at du fullfører nettleserpåloggingen (opptil **5
  minutter**).
- `POST /v1/nxt/graphql` tar så lang tid som Business NXT bruker; store lesinger kan ta
  mange sekunder. Gi HTTP-klienten en romslig timeout (minutter, ikke sekunder).

### Sikkerhetsbegrensninger (hvorfor en nettleser-`fetch` avvises)

Companionen håndhever en loopback-tillitsgrense:

- Forespørsler med en **`Origin`**-header avvises (`403`) — derfor kan ikke en nettsides
  `fetch()` nå den. Kall den fra en lokal prosess (WinHTTP, `HttpClient`,
  `curl`, Python, PowerShell, en VBA `WinHttp.WinHttpRequest`), ikke fra nettleser-
  JavaScript.
- **`Host`**-headeren må være `127.0.0.1`, `localhost` eller `::1`.
- Manglende eller feil **nøkkel** returnerer `401`.

---

## 4. Endepunktreferanse

Hver forespørsel trenger `X-DataLink-Key`. Response-bodyer fra selve Data Link er
JSON med feltnavn i **camelCase**; `POST /v1/nxt/graphql` returnerer Business NXTs
body uendret.

| Metode | Path | Formål |
|--------|------|--------|
| `GET` | [`/v1/status`](#get-v1status) | Helse, versjon og påloggingsstatus. |
| `POST` | [`/v1/auth/login`](#post-v1authlogin) | Interaktiv nettleserpålogging. |
| `POST` | [`/v1/auth/logout`](#post-v1authlogout) | Avslutt Business NXT-sesjonen. |
| `GET` | [`/v1/companies`](#get-v1companies) | Selskaper påloggingen har tilgang til, med rettighetene dine i hvert selskap. |
| `POST` | [`/v1/nxt/graphql`](#post-v1nxtgraphql-gatewayen) | Vilkårlig Business NXT GraphQL-query eller mutation, rettighetssjekket. |

### `GET /v1/status`

Helse og påloggingsstatus. Den fungerer når du er logget ut.

```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 den interaktive nettleserpåloggingen og returnerer når den er ferdig. Ingen body. Den
åpner **alltid** nettleseren, også når du allerede er logget på — sjekk
`GET /v1/status` først hvis du bare vil logge på ved behov.

```json
{ "authenticated": true }
```

Hvis nettleserflyten ikke fullføres innen 5 minutter, feiler kallet med
`503 unavailable` (`retryable: true`); en pålogging som Visma Connect avviser, returnerer
`401 unauthorized`.

### `POST /v1/auth/logout`

Avslutter Business NXT-sesjonen. Returnerer `{ "authenticated": false }`.

### `GET /v1/companies`

Selskapene gjeldende pålogging har tilgang til (Business NXTs `availableCompanies`),
hvert med sin **tenant** (Visma.net-kunden det tilhører) og
[Data Link-tilgangen og -tillatelsene](#data-link-tilgang-og-tillatelser) som gjelder der — slik kan du slå opp et
selskaps **navn** til **nummeret** og se hva du kan gjøre før du prøver.

```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 }
  ]
}
```

Et selskap listet med `canRead: false` er et selskap Visma-påloggingen din kan se,
men som Data Link ikke åpner for deg (se [seksjon 5](#data-link-tilgang-og-tillatelser)).

### `POST /v1/nxt/graphql` — gatewayen

Kjører **hvilken som helst** 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åkrevd | Betydning |
|------|---------|-----------|
| `query` | ja | GraphQL-dokumentet (string). |
| `variables` | nei | Et JSON-objekt med variabelverdier. |
| `operationName` | nei | Hvilken operasjon som skal kjøres når dokumentet har flere. |
| `extensions` | nei | Standard GraphQL-extension-data, videresendt uendret. Rettighetssjekken leser den ikke. |

Dette skjer:

1. Data Link **parser** dokumentet og finner ut hvert selskap det leser,
   skriver eller sletter i ([seksjon 6](#hvordan-rettighetssjekken-leser-et-dokument)).
2. Den **kontrollerer Data Link-tilgangen og -tillatelsene dine** for hvert av dem. Hvis noe ikke er
   tillatt, **avvises hele dokumentet**, og ingenting når Business NXT.
3. Ellers **videresender den request-bodyen din uendret** til Business NXT under
   Visma-påloggingen din og returnerer Business NXTs **HTTP-status og body ordrett** —
   inkludert 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 Visma-tokenet ditt selv og venter og prøver på nytt når Business NXT svarer
`429 Too Many Requests` — begge deler er trygt fordi forespørselen ikke ble kjørt.
Den prøver **aldri** på nytt ellers, så en mutation kan aldri kjøres to ganger; en
`5xx` fra Business NXT sendes tilbake til deg som den er.

---

## 5. Data Link-tilgang og tillatelser

Hva du kan gjøre gjennom Data Link administreres av Exsitec i **NxtPlatform
admin**. Den gamle portalen for Config API-tildelinger er borte; docs/download-verten gir ikke
tilgang.


| Hva | Tildeles | Tillater |
|-----|----------|----------|
| **Data Link-tilgang** | Per **tenant** (en Visma.net-kunde) gjennom Data Link-modulen — for alle brukere eller valgte grupper, med overstyring per bruker. | Lesing av alle selskaper i den tenanten (`canRead`). Uten dette avvises **alt** på tenantens selskaper, også lesing. |
| **Skrive**-tillatelse | Gjennom NxtPlatform-roller. | Mutations som oppretter, oppdaterer, behandler eller rapporterer (`*_create`, `*_update`, `*_processings`, `*_reports`, edit-cache-kontroll) — på tenants der du har Data Link-tilgang (`canWrite`). |
| **Slette**-tillatelse | Gjennom NxtPlatform-roller. | Mutations som sletter (`*_delete`) — på tenants der du har Data Link-tilgang (`canDelete`). Sletting krever Slette, ikke Skrive. |

Data Link **begrenser** bare hva din egen Visma-bruker kan gjøre — den gir aldri
tilgang som Visma ikke ville gitt. Et selskap utenfor dine `availableCompanies`
avvises selv om du har tilgang til tenanten.

Når du ikke har Skrive-tillatelse noe sted, ber Data Link også Visma om en
**skrivebeskyttet** pålogging neste gang du logger på, slik at Business NXT selv også avviser
mutations.

Tilgangen og tillatelsene dine hentes fra NxtPlatform når du logger på og
oppdateres med noen minutters mellomrom, og lagres bare i minnet. Hvis NxtPlatform ikke kan
nås, fortsetter siste svar å gjelde en kort nådeperiode; etter det — eller hvis
Data Link aldri har nådd NxtPlatform siden den startet — avviser gatewayen med
`503 unavailable` (`retryable: true`) i stedet for å kjøre ukontrollert.

---
## 6. Hvordan rettighetssjekken leser et dokument

Sjekken arbeider på det **parsede** dokumentet, aldri på teksten, så aliaser,
fragments og variabler kan ikke skjule noe. Den er bevisst streng:

### Navngi selskapet: `useCompany(no: {companyNo})`

Alle selskapsavgrensede lesinger eller skrivinger ligger under en rot-
`useCompany(no: …)`. `no` er selskapsnummeret fra `GET /v1/companies` — en literal, en variabel eller en
variabeldefault:

```graphql
query Projects($company: Int!) {
  useCompany(no: $company) {
    orgUnit2(filter: { orgUnit2No: { _eq: 2130 } }) { items { orgUnit2No name } }
  }
}
```

(Business NXTs organisatoriske enheter får nye navn per selskap; i *Demo - Junge
Byg Aps 9999* er prosjekter `orgUnit2`.)

- Et dokument kan navngi **flere selskaper**, vanligvis med aliaser
  (`demo: useCompany(no: 4926238) { … } junge: useCompany(no: 6307615) { … }`).
  Hvert kontrolleres; én avvisning avviser hele dokumentet.
- `no` må løses til et heltall. Manglende `no`, `null`, en string eller en
  udefinert variabel avvises med `400 invalid_request`.
- **Hver operasjon** i dokumentet kontrolleres, ikke bare den
  `operationName` velger.
- **Fragments** og inline fragments utvides; `@skip` og `@include` ignoreres,
  så en betinget selection teller fortsatt.

### Hva hver del av et dokument trenger

| I dokumentet | Trenger |
|--------------|---------|
| `query { useCompany(no: N) { … } }` | Lesing på selskap Ns tenant. |
| `mutation { useCompany(no: N) { x_create / x_update / x_processings / x_reports / finishEditCache … } }` | Skrive på Ns tenant. |
| `mutation { useCompany(no: N) { x_delete … } }` | Slette på Ns tenant. |
| `query { useCustomer(no: T) { … } }` | Lesing på tenant T. |
| `mutation { useCustomer(…) }` | **Avvises alltid** — endringer på kundenivå (brukere, selskaper) gjøres ikke gjennom Data Link. |
| `mutation { asyncQuery(query: "…", args: {…}) }` | Det innebygde dokumentet trenger, kontrollert etter de samme reglene. |
| `availableCompanies`, `availableCustomers`, `useModel`, `api`, `asyncRequests`, `asyncResult`, `asyncQuery_delete`, introspection (`__schema`, `__type`) | Bare pålogging. |
| Alle andre root-felt, eller en `subscription` | Avvises (`403 forbidden`). |

### Sikker skriving

Business NXT har ingen transaksjoner på tvers av tabeller. To vaner gjør en skriving forutsigbar:

- Sett `onError: FAIL_TABLE` på hver tabelloperasjon, slik at en dårlig rad avviser hele
  tabelloperasjonen i stedet for å skrive deler av den.
- For å få flere tabelloperasjoner til å lykkes eller feile samlet, bruk Business NXTs
  **edit cache**: kjør dem med `useCompany(no: N, persistEditCache: true,
  partialRequestId: "…")` og avslutt med `finishEditCache(action: SAVE)` (eller
  `DISCARD`). Hver forespørsel rettighetssjekkes for seg, som alle andre mutations.

---

## 7. Feil

Det finnes to typer feil, og begge bruker GraphQLs feilformat.

**Business NXTs egne feil** — en GraphQL-feil, en verdi Business NXT avviser, en
rate limit — kommer tilbake **nøyaktig slik Business NXT sendte dem**: HTTP-statusen og
bodyen `{ "data": …, "errors": [ … ] }`.

**Avvisninger fra selve Data Link** (forespørselen nådde aldri 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 / hva du gjør |
|--------|------|-------------------------|
| `invalid_request` | 400 | Body er ikke JSON, mangler `query`, har members annet enn `query`/`variables`/`operationName`/`extensions` eller gjentatte property names (også når de bare skiller på store/små bokstaver), `variables` er ikke et objekt, dokumentet kan ikke parses, eller et selskapsnummer kan ikke løses. Rett og send på nytt. |
| `unauthorized` | 401 | Manglende/ugyldig nøkkel, ikke logget på, eller Visma avviste påloggingen. Les `endpoint.json` på nytt; logg på. |
| `forbidden` | 403 | Data Link-tilgangen eller -tillatelsene dine tillater ikke dette på et selskap (`extensions.company` navngir det), selskapet er ikke tilgjengelig for deg, eller dokumentet bruker en avvist root (`useCustomer` mutation, ukjent root, subscription). Gjelder også cross-origin-/host-blokkeringer. |
| `unavailable` | 503 | Rettighetene dine kunne ikke hentes (`retryable: true`), eller Data Link er opptatt. Prøv igjen om litt. |
| `internal_error` | 500 | Uventet. Sjekk loggmappen (tray → **Open logs folder**). |

`GET /v1/status`, `/v1/auth/*` og `GET /v1/companies` rapporterer feilene sine i
Data Links enkle envelope i stedet:

```json
{ "code": "unauthorized", "message": "…", "retryable": false, "instanceId": "3f2a…" }
```

En ukjent path får en ren `404`.

---

## 8. Forholdet til Business NXT GraphQL API

Data Link **er** Business NXTs GraphQL API, nådd gjennom din egen maskin:

- **Samme schema, samme dokumenter.** Alt du kan sende til
  `https://business.visma.net/api/graphql`, sender du til `/v1/nxt/graphql`, underlagt
  [rettighetssjekken](#hvordan-rettighetssjekken-leser-et-dokument). Bruk Vismas
  Business NXT-dokumentasjon, eller introspection gjennom gatewayen, for tabeller,
  felter og filtre.
- **Ingen token å håndtere.** Data Link holder Visma Connect-påloggingen din og legger
  den på hver videresendte forespørsel; programmet ditt ser aldri et token.
- **Styrt.** Hvert dokument kontrolleres mot Data Link-tilgangen og -tillatelsene dine før det
  forlater maskinen, så Exsitec kan bestemme per tenant hvem som kan lese, skrive og
  slette gjennom Data Link.
- **Ingenting formes om.** Svaret er Business NXTs, byte for byte.

---

## 9. Bruke den fra Excel

Last ned og importer de to VBA-modulene nedenfor (**Alt+F11** -> **File** ->
**Import File...**). De er standardmoduler og bruker bare late binding, så ingen
endringer i **Tools -> References** trengs.

| Fil | Hva den er |
|-----|------------|
| [`ExsitecDataLink.bas`](/docs/vba/ExsitecDataLink.bas) | Den faste SDK-en: autostart, oppdagelse, pålogging, GraphQL, JSON, feil og utskrift til regneark. |
| [`ExsitecDataLinkCustom.bas`](/docs/vba/ExsitecDataLinkCustom.bas) | Arbeidsbokmalen din: `COMPANY_NO` og eksempelmakroer for knapper. Endre denne filen per kunde/arbeidsbok. |

### Offentlig 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 bruker `$company` og `vars` ikke har `company`, legger SDK-en til `COMPANY_NO`. |
| `NxtVars(name, value, ...) As Object` | Bygger en variabel-dictionary. Eksempel: `NxtVars("company", 4926238, "first", 20)`. |
| `NxtItems(data, path) As Collection` | Går en prikket path som `useCompany.generalLedgerAccount` og returnerer `items`-collectionen. `items: null` blir en tom collection. |
| `WriteRows(items, target, [columns], [includeHeader]) As Range` | Skriver rader til Excel i én blokk. Nestede objekter utvides ett nivå (`a.b`), ISO-datoer blir Excel-datoer, null blir tomt, og tekst som ser ut som formler forblir tekst. |
| `ShowNxtError()` | Viser `Err.Description` i en vennlig meldingsboks. Bruk den i error handlers. |
| `NxtUseEndpointFile(path)` | Avansert/test-hook for en ikke-standard oppdagelsesfil. |
| `NxtUseLanguage(languageCode)` | Avansert/test-hook som overstyrer SDK-ens meldingsspråk (`en`, `da`, `sv`, `nb`). |
| `NxtUseStartPaths(primaryExe, fallbackExe)` | Avansert/test-hook for ikke-standard paths til Data Link-executable. |

### Knappemakromønster

Legg selskapsspesifikke innstillinger og knappemakroer 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
```

For mutations kaller du samme `NxtQuery`-funksjon med et `mutation`-dokument. SDK-en
prøver aldri mutations på nytt etter portendring eller transportfeil.

### Autostart og pålogging

SDK-en leser `%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json`. Hvis filen mangler
eller `/v1/status` ikke svarer, starter den `%LOCALAPPDATA%\Exsitec.DataLink\Exsitec Data Link.exe`
(eller `current\DesktopDataLink.Host.exe`) og venter opptil 30 sekunder.

Før hvert kall sjekker den `/v1/status`. Hvis du er logget ut, forteller Excel at
nettleseren åpnes, kaller `/v1/auth/login`, og kaster deretter en feil som ber deg kjøre
handlingen på nytt når påloggingen er fullført. Den opprinnelige GraphQL-forespørselen kjøres ikke
automatisk etter pålogging.

### VBA-feilnumre

| Konstant | Betydning |
|----------|-----------|
| `NXT_ERR_NOT_RUNNING` | Data Link er ikke installert, kunne ikke startes eller svarte aldri. |
| `NXT_ERR_NOT_SIGNED_IN` | Du er logget ut, avbrøt påloggingen, eller pålogging krever at handlingen kjøres på nytt. |
| `NXT_ERR_FORBIDDEN` | Data Link-tilgang/-tillatelser forbyr forespørselen eller selskapet. |
| `NXT_ERR_INVALID` | GraphQL-forespørselen er malformed eller ikke tillatt av gatewayen. |
| `NXT_ERR_NXT` | Business NXT returnerte GraphQL-feil; `Err.Description` er den første Business NXT-meldingen. |
| `NXT_ERR_UNAVAILABLE` | Data Link, Business NXT eller rettighetstjenesten er midlertidig utilgjengelig. |
| `NXT_ERR_OTHER` | Alle andre ikke-vellykkede svar. |

Meldinger bruker språkinnstillingen i Data Link-tray (`ui-settings.json`) for engelsk,
dansk, svensk og norsk, med engelsk som standard.

---

## 10. Eksempler i andre språk

### PowerShell

```powershell
# 1. Finn den kjørende companionen.
$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. Sjekk status / pålogging.
Invoke-RestMethod "$base/status" -Headers $hdr

# 3. Slå opp et selskapsnavn til nummeret, og se rettighetene dine der.
$companies = (Invoke-RestMethod "$base/companies" -Headers $hdr).available
$company   = $companies | Where-Object { $_.name -eq "Exsitec BNXT Demo" }

# 4. Kjør 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 standardbiblioteket:

```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 inneholder GraphQL-feilene
        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. Feilsøking

| Symptom | Sannsynlig årsak | Hva du gjør |
|---------|------------------|-------------|
| Ingen `endpoint.json`, eller VBA `NXT_ERR_NOT_RUNNING` | Data Link er ikke installert eller svarte ikke. | VBA SDK-en starter den automatisk; hvis det feiler, installer den fra docs/download-siden. |
| Tilkobling nektet etter at det fungerte før | Data Link startet på nytt eller ble avsluttet (f.eks. etter en oppdatering): ny port og nøkkel. | Les `endpoint.json` på nytt. |
| `401` «Det kreves en gyldig connector-nøkkel.» | Manglende eller utdatert nøkkel. | Les `endpoint.json` på nytt — nøkkelen endres ved hver start. |
| `401` «Ikke logget på Business NXT …» | Ikke logget på, eller sesjonen er avsluttet. | Tray → **Sign in** (eller `POST /v1/auth/login`). |
| `403` «Cross-origin-forespørsler er ikke tillatt.» | Kallet kom fra nettleser-JavaScript. | Kall fra et lokalt program eller skript i stedet. |
| `403` «Du har ikke Data Link-tilgang til …» | Du har ikke Data Link-tilgang på selskapets tenant. | Be Exsitec-kontakten din om Data Link-tilgang til den tenanten. |
| `403` «Du har ikke Data Link Skrive-/Slette-tillatelse …» | NxtPlatform-rollene dine mangler den tillatelsen (`canWrite` / `canDelete`). | Spør Exsitec-kontakten din; sjekk `GET /v1/companies`. |
| `403` «Selskap … er ikke tilgjengelig for den påloggede brukeren.» | Visma-brukeren din kan ikke se selskapet, eller nummeret har en skrivefeil. | Sjekk `GET /v1/companies`; logg ut og inn med riktig Visma-bruker. |
| `400` «… argumentet 'no' må løses til et heltall.» | `useCompany` uten nummer, eller en variabel du ikke sendte. | Send selskapsnummeret som en `Int` literal eller variabel. |
| `503` «Data Link-rettigheter kunne ikke hentes …» | NxtPlatform er utilgjengelig. | Prøv igjen om litt. |
| `503` «Pålogging fikk tidsavbrudd …» | Nettleserpåloggingen ble ikke fullført innen 5 minutter. | Prøv igjen og fullfør den i nettleseren. |
| Excel viser «Svarer ikke» under pålogging | `/v1/auth/login` venter på nettleseren. | Fullfør påloggingen i nettleseren; kjør deretter Excel-handlingen på nytt. |

Fortsatt fast? Tray → **Open logs folder** og send den nyeste loggfilen til
Exsitec-kontakten din — den inneholder ingen GraphQL-dokumenter, beløp, nøkler eller token.

---

## 12. Ordliste

| Begrep | Betydning |
|--------|-----------|
| **Data Link** (companion) | Tray-appen på PC-en din som logger på Business NXT og tilbyr den lokale gatewayen. |
| **NXT Gateway** | `POST /v1/nxt/graphql`: Business NXT GraphQL, rettighetssjekket og videresendt under påloggingen din. |
| **Rettighetssjekk** | Gatewayens kontroll av et parset GraphQL-dokument mot Data Link-tilgangen og -tillatelsene dine, alt eller ingenting. |
| **Data Link-tilgang** | Retten din til å bruke Data Link i det hele tatt på en tenants selskaper (`canRead`), tildelt i NxtPlatform admins Data Link-modul. |
| **Data Link-tillatelse** | Skrive eller Slette (`canWrite` / `canDelete`), tildelt gjennom NxtPlatform-roller; den gjelder bare på tenants der du har Data Link-tilgang. |
| **Tenant** | En Visma.net-kunde; Data Link-tilgang tildeles per tenant. |
| **Selskap** | Et Business NXT-selskap, identifisert med nummeret sitt og navngitt i hvert dokument med `useCompany(no: …)`. |
| **Oppdagelsesfil** | `endpoint.json`: der kjørende Data Link publiserer port, nøkkel og instance id. |
| **Nøkkel** | Hemmeligheten per oppstart som hver forespørsel må ha med; den endres ved hver start. |
| **Instance id** | Identifiserer én kjøring av Data Link; en ny verdi betyr at den startet på nytt. |

---

*Dette dokumentet beskriver det lokale `v1`-API-et. Companionen rapporterer kjørende
versjon i `GET /v1/status` og på tray-menyen.*
