# Exsitec Data Link — Användning

**Exsitec Data Link** är en liten Windows-kompanjon som låter ett lokalt program —
en Excel-arbetsbok, ett skript eller ditt eget verktyg — köra **Visma Business NXT GraphQL**-
queries och mutations utan att själv hantera Visma-inloggning eller token. Den loggar in i
Business NXT åt dig och exponerar en **styrd GraphQL-gateway på din egen dator**
(`localhost`): du skickar ett GraphQL-dokument, Data Link kontrollerar dina **Data Link-
rättigheter** för varje företag som dokumentet berör och vidarebefordrar det till Business NXT
under din egen Visma-inloggning. Svaret från Business NXT returneras oförändrat.

> **Läser du detta med en AI-assistent?** Den här sidan är den kompletta
> användningsreferensen. Du kan klistra in den i din assistent, eller ge den rå Markdown på
> **`/docs/usage.md?lang=sv`** (samma innehåll, utan formatering). Be den skriva anropen på
> det språk du föredrar — allt den behöver (endpoints, headers, rättighetsregler,
> request- och response-format samt felformat) finns nedan. För själva Business NXT-schemat
> (tabeller, fält, filter), peka den mot Vismas Business NXT GraphQL-dokumentation eller be
> den köra en introspection-query genom gatewayen.

---

## 1. Kom igång snabbt

1. **Installera och logga in.** Installera Data Link och högerklicka sedan på dess **tray-ikon**
   (nere till höger, nära klockan) → **Logga in**. Webbläsaren öppnar Visma Connect;
   logga in där.
2. **Hitta porten och nyckeln.** Läs
   `%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json` — den innehåller loopback-`port`
   och en `key` som skapas vid varje start.
3. **Säg hej.** `GET http://127.0.0.1:{port}/v1/status` med headern
   `X-DataLink-Key: {key}`. Du får versionen och om du är inloggad.
4. **Välj ett företag.** `GET /v1/companies` listar företagen du kan komma åt, med
   vad din Data Link-åtkomst och dina behörigheter låter dig göra i varje företag.
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 är Business NXT:s egen `{ "data": …, "errors": … }`.

---

## 2. Så körs den

- Data Link installeras **per användare** och startar automatiskt när du loggar in i
  Windows. Den körs tyst i bakgrunden och visar en liten **systemfältsikon**
  (nere till höger i aktivitetsfältet, nära klockan).
- **Högerklicka på systemfältsikonen** för att:
  - se om du är **Inloggad** eller **Utloggad**,
  - **Logga in** / **Logga ut** (inloggning öppnar webbläsaren till Visma Connect),
  - **Öppna loggmapp**, **Sök efter uppdateringar…**, öppna denna **användningsdokumentation**
    och **Avsluta**.
- Du måste vara **inloggad** innan någon GraphQL-request kan lyckas. Inloggningen är
  interaktiv (ett webbläsarfönster); därefter förnyas sessionen automatiskt och
  överlever omstarter av Data Link.
- Kompanjonen har **inget ”aktuellt företag”.** Varje GraphQL-dokument namnger de
  företag det arbetar med (`useCompany(no: …)`), så en körande kompanjon betjänar
  dem alla.
- **Uppdateringar:** tray → **Sök efter uppdateringar…** laddar ned och installerar en ny version i
  ett steg och startar om Data Link (en notifiering bekräftar den nya versionen). En
  omstart ger den en ny port och nyckel — se *omstartssignalen* i
  [avsnitt 3](#svarsrubriker).

### Var saker finns

| Vad | Var |
|------|-------|
| Discovery-fil (port + nyckel) | `%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json` — kan bara läsas av dig. |
| Loggar | `%LOCALAPPDATA%\Exsitec\DataLink\logs\` (tray → **Öppna loggmapp**). Loggar innehåller aldrig GraphQL-dokument, belopp, nycklar eller tokens. |
| Inloggningssession | `session.dat` i samma mapp, krypterad för din Windows-användare. |
| Själva appen | `%LOCALAPPDATA%\Exsitec.DataLink\` (per användare-installation som uppdaterar sig själv). |

---

## 3. Prata med det lokala API:et

### Discovery — hitta porten och nyckeln

Kompanjonen väljer en ledig loopback-port vid start och skriver en liten **discovery-
fil** så att betrodda lokala anropare kan hitta 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:ets bas-URL är `http://127.0.0.1:{port}`.
- **`key`** — den delade hemlighet som skapas vid varje start. Skicka den på **varje** request (se nedan).
  Den ändras varje gång kompanjonen startar om, så läs den på nytt — lagra den aldrig.
  Tolka filen med en JSON-parser: tecken som `+` kan skrivas som
  `\u002B`-escapes.
- **`instanceId`** — identifierar denna körning av kompanjonen (”omstartssignalen”;
  se nedan).

### Bas-URL och versionering

```
http://127.0.0.1:{port}/v1/…
```

Alla endpoints ligger under prefixet `/v1` (fältet `protocol` i discovery-
filen); GraphQL-gatewayen är `/v1/nxt/graphql`. Bind mot `127.0.0.1` (loopback) —
kompanjonen lyssnar bara där.

### Obligatoriska request-headers

| Header | När | Värde |
|--------|------|-------|
| `X-DataLink-Key` | **Varje** request | `key` från `endpoint.json`. |
| `Content-Type: application/json` | Requests med JSON-body | Skicka bodyn som UTF-8. |

Det finns ingen företagsheader: företaget anges inne i GraphQL-dokumentet.

### Svarsrubriker

| Header | Betydelse |
|--------|---------|
| `X-DataLink-Instance` | Kompanjonens `instanceId` för denna körning. |

**Omstartssignal.** Om värdet i `X-DataLink-Instance` **ändras** mellan anrop
har kompanjonen startat om. Läs `endpoint.json` på nytt — både porten **och** nyckeln kan ha
ändrats.

### Tidsgränser

- `POST /v1/auth/login` väntar på att du slutför webbläsarinloggningen (upp till **5
  minuter**).
- `POST /v1/nxt/graphql` tar så lång tid som Business NXT tar; stora läsningar kan ta
  många sekunder. Ge din HTTP-klient en generös timeout (minuter, inte sekunder).

### Säkerhetsbegränsningar (varför en webbläsar-`fetch` avvisas)

Kompanjonen upprätthåller en trust boundary för loopback:

- Requests med en **`Origin`**-header avvisas (`403`) — därför kan en webbsidas
  `fetch()` inte nå den. Anropa den från en lokal process (WinHTTP, `HttpClient`,
  `curl`, Python, PowerShell, en VBA `WinHttp.WinHttpRequest`), inte från
  JavaScript i webbläsaren.
- **`Host`**-headern måste vara `127.0.0.1`, `localhost` eller `::1`.
- En saknad eller felaktig **nyckel** returnerar `401`.

---

## 4. Endpoint-referens

Varje request behöver `X-DataLink-Key`. Response-bodys från själva Data Link är
JSON med fältnamn i **camelCase**; `POST /v1/nxt/graphql` returnerar Business NXT:s
body oförändrad.

| Metod | Sökväg | Syfte |
|--------|------|---------|
| `GET` | [`/v1/status`](#get-v1status) | Hälsa, version och inloggningsstatus. |
| `POST` | [`/v1/auth/login`](#post-v1authlogin) | Interaktiv webbläsarinloggning. |
| `POST` | [`/v1/auth/logout`](#post-v1authlogout) | Avsluta Business NXT-sessionen. |
| `GET` | [`/v1/companies`](#get-v1companies) | Företag som inloggningen kan komma åt, med dina rättigheter i varje. |
| `POST` | [`/v1/nxt/graphql`](#post-v1nxtgraphql-gatewayen) | Valfri Business NXT GraphQL-query eller mutation, rättighetskontrollerad. |

### `GET /v1/status`

Hälsa och inloggningsstatus. Den fungerar även när du är utloggad.

```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`

Startar den interaktiva webbläsarinloggningen och returnerar när den är klar. Ingen body. Den
**öppnar alltid** webbläsaren, även när du redan är inloggad — kontrollera
`GET /v1/status` först om du bara vill logga in vid behov.

```json
{ "authenticated": true }
```

Om webbläsarflödet inte slutförs inom 5 minuter misslyckas anropet med
`503 unavailable` (`retryable: true`); en inloggning som Visma Connect avvisar returnerar
`401 unauthorized`.

### `POST /v1/auth/logout`

Avslutar Business NXT-sessionen. Returnerar `{ "authenticated": false }`.

### `GET /v1/companies`

Företagen som den aktuella inloggningen kan komma åt (Business NXT:s `availableCompanies`),
varje med sin **tenant** (Visma.net-kunden det tillhör) och den
[Data Link-åtkomst och de behörigheter](#data-link-atkomst-och-behorigheter) som gäller där — så att du kan slå upp ett
företags **namn** till dess **nummer** och se vad du får göra innan du försöker.

```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 }
  ]
}
```

Ett företag som listas med `canRead: false` är ett företag som din Visma-inloggning kan se men som
Data Link inte öppnar för dig (se [avsnitt 5](#data-link-atkomst-och-behorigheter)).

### `POST /v1/nxt/graphql` — gatewayen

Kör **valfri** Business NXT GraphQL-query eller mutation. Bodyn är standard
GraphQL-over-HTTP:

```json
{
  "query": "query Accounts($company: Int!) { useCompany(no: $company) { generalLedgerAccount(first: 5) { items { accountNo name } } } }",
  "variables": { "company": 4926238 },
  "operationName": "Accounts"
}
```

| Fält | Obligatoriskt | Betydelse |
|-------|----------|---------|
| `query` | ja | GraphQL-dokumentet (sträng). |
| `variables` | nej | Ett JSON-objekt med variabelvärden. |
| `operationName` | nej | Vilken operation som ska köras när dokumentet har flera. |
| `extensions` | nej | Standard GraphQL extension-data, vidarebefordras oförändrad. Rättighetskontrollen läser den inte. |

Vad som händer:

1. Data Link **parsar** dokumentet och räknar ut varje företag det läser,
   skriver eller tar bort i ([avsnitt 6](#sa-laser-rattighetskontrollen-ett-dokument)).
2. Den **kontrollerar din Data Link-åtkomst och dina behörigheter** för vart och ett. Om något inte är
   tillåtet avvisas **hela dokumentet** och inget når Business NXT.
3. Annars **vidarebefordrar den din request-body oförändrad** till Business NXT under din
   Visma-inloggning och returnerar Business NXT:s **HTTP-status och body ordagrant** —
   inklusive Business NXT:s egna `errors` och `extensions` (till exempel
   `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 förnyar din Visma-token själv och, när Business NXT svarar
`429 Too Many Requests`, väntar och försöker igen — båda är säkra eftersom requesten inte
kördes. Den försöker **aldrig** igen med något annat, så en mutation kan aldrig köras två gånger; ett
`5xx` från Business NXT skickas tillbaka till dig som det är.

---
## 5. Data Link-åtkomst och behörigheter

Vad du får göra genom Data Link administreras av Exsitec i **NxtPlatform
admin**. Den gamla Config API-portalen för behörighetstilldelning är borta; docs/download-värden ger inte
åtkomst.


| Vad | Tilldelas | Tillåter |
|------|---------|--------|
| **Data Link-åtkomst** | Per **tenant** (en Visma.net-kund) genom Data Link-modulen — för alla användare eller valda grupper, med åsidosättning per användare. | Läsning av valfritt företag i den tenant (`canRead`). Utan den avvisas **allt** på tenantens företag, även läsningar. |
| **Skriv**-behörighet | Genom NxtPlatform-roller. | Mutations som skapar, uppdaterar, processar eller rapporterar (`*_create`, `*_update`, `*_processings`, `*_reports`, edit-cache-kontroll) — på tenants där du har Data Link-åtkomst (`canWrite`). |
| **Radera**-behörighet | Genom NxtPlatform-roller. | Mutations som raderar (`*_delete`) — på tenants där du har Data Link-åtkomst (`canDelete`). Radering kräver Radera, inte Skriv. |

Data Link **begränsar** bara vad din egen Visma-användare får göra — den ger aldrig
åtkomst som Visma inte skulle ge. Ett företag utanför dina `availableCompanies` avvisas
även om du har åtkomst till dess tenant.

När du inte har Skriv-behörighet någonstans ber Data Link också Visma om en
**read-only**-inloggning nästa gång du loggar in, så Business NXT själv avvisar
mutations också.

Din åtkomst och dina behörigheter hämtas från NxtPlatform när du loggar in och
uppdateras med några minuters mellanrum, och sparas bara i minnet. Om NxtPlatform inte kan
nås fortsätter det senaste svaret att gälla under en kort respitperiod; efter det — eller
om Data Link aldrig har nått NxtPlatform sedan den startade — avvisar gatewayen
med `503 unavailable` (`retryable: true`) i stället för att köra okontrollerat.

---
## 6. Så läser rättighetskontrollen ett dokument

Kontrollen arbetar med det **parsade** dokumentet, aldrig med dess text, så alias,
fragments och variabler kan inte dölja något. Den är avsiktligt strikt:

### Ange företaget: `useCompany(no: {companyNo})`

Varje företagsavgränsad läsning eller skrivning ligger under roten `useCompany(no: …)`. `no`
är företagsnumret från `GET /v1/companies` — en literal, en variabel eller ett
variabel-defaultvärde:

```graphql
query Projects($company: Int!) {
  useCompany(no: $company) {
    orgUnit2(filter: { orgUnit2No: { _eq: 2130 } }) { items { orgUnit2No name } }
  }
}
```

(Business NXT:s organisatoriska enheter får nya etiketter per företag; i *Demo - Junge
Byg Aps 9999* är projekt `orgUnit2`.)

- Ett dokument kan namnge **flera företag**, normalt med alias
  (`demo: useCompany(no: 4926238) { … } junge: useCompany(no: 6307615) { … }`).
  Varje företag kontrolleras; ett avslag avslår hela dokumentet.
- `no` måste lösas till ett heltal. Ett saknat `no`, `null`, en sträng eller en
  odefinierad variabel avvisas med `400 invalid_request`.
- **Varje operation** i dokumentet kontrolleras, inte bara den som
  `operationName` väljer.
- **Fragments** och inline fragments expanderas; `@skip` och `@include` ignoreras,
  så ett villkorligt urval räknas ändå.

### Vad varje del av ett dokument kräver

| I dokumentet | Kräver |
|-----------------|-------|
| `query { useCompany(no: N) { … } }` | Läsning på företag N:s tenant. |
| `mutation { useCompany(no: N) { x_create / x_update / x_processings / x_reports / finishEditCache … } }` | Skrivning på N:s tenant. |
| `mutation { useCompany(no: N) { x_delete … } }` | Radering på N:s tenant. |
| `query { useCustomer(no: T) { … } }` | Läsning på tenant T. |
| `mutation { useCustomer(…) }` | **Avvisas alltid** — ändringar på kundnivå (användare, företag) görs inte genom Data Link. |
| `mutation { asyncQuery(query: "…", args: {…}) }` | Det som det inbäddade dokumentet kräver, kontrollerat med samma regler. |
| `availableCompanies`, `availableCustomers`, `useModel`, `api`, `asyncRequests`, `asyncResult`, `asyncQuery_delete`, introspection (`__schema`, `__type`) | Endast en inloggning. |
| Alla andra rotfält, eller en `subscription` | Avvisas (`403 forbidden`). |

### Skriv säkert

Business NXT har inga transaktioner över flera tabeller. Två vanor gör en skrivning förutsägbar:

- Sätt `onError: FAIL_TABLE` på varje tabelloperation, så att en dålig rad avvisar hela
  tabelloperationen i stället för att skriva en del av den.
- För att få flera tabelloperationer att lyckas eller misslyckas tillsammans, använd Business NXT:s
  **edit cache**: kör dem med `useCompany(no: N, persistEditCache: true,
  partialRequestId: "…")` och avsluta med `finishEditCache(action: SAVE)` (eller
  `DISCARD`). Varje request rättighetskontrolleras för sig, som alla andra mutations.

---

## 7. Fel

Det finns två slags fel, och båda använder GraphQL:s felformat.

**Business NXT:s egna fel** — ett GraphQL-fel, ett värde som Business NXT avvisar, en
rate limit — kommer tillbaka **exakt som Business NXT skickade dem**: dess HTTP-status och
body `{ "data": …, "errors": [ … ] }`.

**Avslag från Data Link själv** (requesten nådde 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 | Betydelse / vad du ska göra |
|--------|------|----------------------|
| `invalid_request` | 400 | Bodyn är inte JSON, saknar `query`, har medlemmar utöver `query`/`variables`/`operationName`/`extensions` eller upprepade property-namn (även om de bara skiljer sig i skiftläge), `variables` är inte ett objekt, dokumentet kan inte parsas, eller ett företagsnummer kan inte lösas. Rätta och skicka igen. |
| `unauthorized` | 401 | Saknad/ogiltig nyckel, inte inloggad, eller Visma avvisade inloggningen. Läs `endpoint.json` på nytt; logga in. |
| `forbidden` | 403 | Din Data Link-åtkomst eller dina behörigheter tillåter inte detta på ett företag (`extensions.company` anger det), företaget är inte tillgängligt för dig, eller dokumentet använder en avvisad root (`useCustomer` mutation, okänd root, subscription). Även cross-origin-/host-blockeringar. |
| `unavailable` | 503 | Dina rättigheter kunde inte hämtas (`retryable: true`), eller Data Link är upptagen. Försök igen snart. |
| `internal_error` | 500 | Oväntat. Kontrollera loggmappen (tray → **Öppna loggmapp**). |

`GET /v1/status`, `/v1/auth/*` och `GET /v1/companies` rapporterar sina fel i
Data Links vanliga envelope i stället:

```json
{ "code": "unauthorized", "message": "…", "retryable": false, "instanceId": "3f2a…" }
```

En okänd sökväg får en enkel `404`.

---

## 8. Relation till Business NXT GraphQL API

Data Link **är** Business NXT:s GraphQL API, nått via din egen dator:

- **Samma schema, samma dokument.** Allt du kan skicka till
  `https://business.visma.net/api/graphql` skickar du till `/v1/nxt/graphql`, med förbehåll för
  [rättighetskontrollen](#sa-laser-rattighetskontrollen-ett-dokument). Använd Vismas
  Business NXT-dokumentation, eller introspection genom gatewayen, för tabeller,
  fält och filter.
- **Inga tokens att hantera.** Data Link håller din Visma Connect-inloggning och lägger till
  den i varje vidarebefordrad request; ditt program ser aldrig någon token.
- **Styrt.** Varje dokument kontrolleras mot din Data Link-åtkomst och dina behörigheter innan det
  lämnar din dator, så Exsitec kan besluta per tenant vem som får läsa, skriva och
  radera genom Data Link.
- **Inget omformas.** Svaret är Business NXT:s, byte för byte.

---

## 9. Använda den från Excel

Ladda ned och importera de två VBA-modulerna nedan (**Alt+F11** -> **Arkiv** ->
**Importera fil...**). De är standardmoduler och använder bara late binding, så inga ändringar i
**Verktyg -> Referenser** behövs.

| Fil | Vad den är |
|------|------------|
| [`ExsitecDataLink.bas`](/docs/vba/ExsitecDataLink.bas) | Det fasta SDK:t: auto-start, discovery, inloggning, GraphQL, JSON, fel och utskrift till kalkylblad. |
| [`ExsitecDataLinkCustom.bas`](/docs/vba/ExsitecDataLinkCustom.bas) | Din arbetsboksmall: `COMPANY_NO` och exempel på knappmakron. Ändra den här filen per kund/arbetsbok. |

### Publikt VBA-API

| API | Syfte |
|-----|---------|
| `NxtQuery(gql, [vars]) As Object` | Skickar en GraphQL-query eller mutation till `POST /v1/nxt/graphql` och returnerar `data`-ordlistan. Om dokumentet använder `$company` och `vars` saknar `company`, lägger SDK:t till `COMPANY_NO`. |
| `NxtVars(name, value, ...) As Object` | Bygger en variabelordlista. Exempel: `NxtVars("company", 4926238, "first", 20)`. |
| `NxtItems(data, path) As Collection` | Går en punktseparerad path som `useCompany.generalLedgerAccount` och returnerar dess `items`-collection. `items: null` blir en tom collection. |
| `WriteRows(items, target, [columns], [includeHeader]) As Range` | Skriver rader till Excel i ett block. Nästlade objekt expanderas en nivå (`a.b`), ISO-datum blir Excel-datum, null blir tomt och text som ser ut som formler förblir text. |
| `ShowNxtError()` | Visar `Err.Description` i en vänlig meddelanderuta. Använd den i felhanterare. |
| `NxtUseEndpointFile(path)` | Avancerad/test-hook för en discovery-fil som inte är standard. |
| `NxtUseLanguage(languageCode)` | Avancerad/test-hook som åsidosätter SDK:ts meddelandespråk (`en`, `da`, `sv`, `nb`). |
| `NxtUseStartPaths(primaryExe, fallbackExe)` | Avancerad/test-hook för Data Link-körbara sökvägar som inte är standard. |

### Mönster för knappmakron

Lägg företagsspecifika inställningar och knappmakron 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
```

För mutations anropar du samma `NxtQuery`-funktion med ett `mutation`-dokument. SDK:t
försöker aldrig köra mutations igen efter en portändring eller ett transportfel.

### Auto-start och inloggning

SDK:t läser `%LOCALAPPDATA%\Exsitec\DataLink\endpoint.json`. Om filen saknas
eller `/v1/status` inte svarar startar det `%LOCALAPPDATA%\Exsitec.DataLink\Exsitec Data Link.exe`
(eller `current\DesktopDataLink.Host.exe`) och väntar upp till 30 sekunder.

Före varje anrop kontrollerar det `/v1/status`. Om du är utloggad talar Excel om att
webbläsaren kommer att öppnas, anropar `/v1/auth/login` och kastar sedan ett fel som säger att du ska köra
åtgärden igen när inloggningen är klar. Den ursprungliga GraphQL-requesten körs inte
automatiskt efter inloggning.

### VBA-felnummer

| Konstant | Betydelse |
|----------|---------|
| `NXT_ERR_NOT_RUNNING` | Data Link är inte installerad, kunde inte startas eller svarade aldrig. |
| `NXT_ERR_NOT_SIGNED_IN` | Du är utloggad, avbröt inloggningen, eller inloggningen kräver att åtgärden körs igen. |
| `NXT_ERR_FORBIDDEN` | Data Link-åtkomst/behörigheter förbjuder requesten eller företaget. |
| `NXT_ERR_INVALID` | GraphQL-requesten är felaktigt formad eller tillåts inte av gatewayen. |
| `NXT_ERR_NXT` | Business NXT returnerade GraphQL-fel; `Err.Description` är det första Business NXT-meddelandet. |
| `NXT_ERR_UNAVAILABLE` | Data Link, Business NXT eller rättighetstjänsten är tillfälligt otillgänglig. |
| `NXT_ERR_OTHER` | Alla andra responses som inte är lyckade. |

Meddelanden använder Data Link-trayens språkinställning (`ui-settings.json`) för engelska,
danska, svenska och norska, med engelska som standard.

---
## 10. Exempel i andra språk

### 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

Endast 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. Felsökning

| Symptom | Trolig orsak | Vad du ska göra |
|---------|--------------|------------|
| Ingen `endpoint.json`, eller VBA `NXT_ERR_NOT_RUNNING` | Data Link är inte installerad eller svarade inte. | VBA-SDK:t startar den automatiskt; om det misslyckas, installera den från docs/download-sidan. |
| Anslutningen nekas efter att den fungerade tidigare | Data Link startade om eller avslutades (t.ex. efter en uppdatering): ny port och ny nyckel. | Läs `endpoint.json` på nytt. |
| `401` "A valid connector key is required." | Nyckeln saknas eller är inaktuell. | Läs `endpoint.json` på nytt — nyckeln ändras vid varje start. |
| `401` "Not signed in to Business NXT…" | Du är inte inloggad, eller sessionen har avslutats. | Tray → **Logga in** (eller `POST /v1/auth/login`). |
| `403` "Cross-origin requests are not permitted." | Anropet kom från JavaScript i webbläsaren. | Anropa från ett lokalt program eller skript i stället. |
| `403` "You have no Data Link Access to …" | Du saknar Data Link-åtkomst på det företagets tenant. | Be din Exsitec-kontakt om Data Link-åtkomst till den tenanten. |
| `403` "You have no Data Link Write / Delete permission…" | Dina NxtPlatform-roller saknar den behörigheten (`canWrite` / `canDelete`). | Be din Exsitec-kontakt; kontrollera `GET /v1/companies`. |
| `403` "Company … is not available to the signed‑in user." | Din Visma-användare kan inte se det företaget, eller så finns ett skrivfel i numret. | Kontrollera `GET /v1/companies`; logga ut och in med rätt Visma-användare. |
| `400` "…'no' argument must resolve to an integer." | `useCompany` utan nummer, eller en variabel som du inte skickade. | Skicka företagsnumret som en `Int`-literal eller variabel. |
| `503` "Data Link rights could not be fetched…" | NxtPlatform kan inte nås. | Försök igen snart. |
| `503` "Sign-in timed out…" | Webbläsarinloggningen slutfördes inte inom 5 minuter. | Försök igen och slutför den i webbläsaren. |
| Excel visar "Svarar inte" under inloggning | `/v1/auth/login` väntar på webbläsaren. | Slutför inloggningen i webbläsaren och kör sedan Excel-åtgärden igen. |

Fortfarande fast? Tray → **Öppna loggmapp** och skicka den senaste loggfilen till din
Exsitec-kontakt — den innehåller inga GraphQL-dokument, belopp, nycklar eller tokens.

---

## 12. Ordlista

| Term | Betydelse |
|------|---------|
| **Data Link** (kompanjon) | Tray-appen på din dator som loggar in i Business NXT och tillhandahåller den lokala gatewayen. |
| **NXT Gateway** | `POST /v1/nxt/graphql`: Business NXT GraphQL, rättighetskontrollerad och vidarebefordrad under din inloggning. |
| **Rättighetskontroll** | Gatewayens kontroll av ett parsat GraphQL-dokument mot din Data Link-åtkomst och dina behörigheter, allt eller inget. |
| **Data Link-åtkomst** | Din rätt att över huvud taget använda Data Link på en tenants företag (`canRead`), tilldelad i NxtPlatform admins Data Link-modul. |
| **Data Link-behörighet** | Skriv eller Radera (`canWrite` / `canDelete`), tilldelad genom NxtPlatform-roller; den gäller bara på tenants där du har Data Link-åtkomst. |
| **Tenant** | En Visma.net-kund; Data Link-åtkomst tilldelas per tenant. |
| **Företag** | Ett Business NXT-företag, identifierat med sitt nummer och namngivet i varje dokument med `useCompany(no: …)`. |
| **Discovery-fil** | `endpoint.json`: där den körande Data Link publicerar sin port, nyckel och instance id. |
| **Nyckel** | Hemligheten som skapas vid varje start och som varje request måste bära; den ändras vid varje start. |
| **Instance id** | Identifierar en körning av Data Link; ett nytt värde betyder att den har startat om. |

---

*Det här dokumentet beskriver det lokala `v1`-API:et. Kompanjonen rapporterar sin körande
version i `GET /v1/status` och i tray-menyn.*
