# DEXPRO AWF Externe Genehmigungen — API-Integrationshandbuch

> **Zielgruppe:** Entwickler, die benutzerdefinierte Genehmigungsoberflächen oder Integrationen aufbauen, die die DEXPRO-AWF-API für externe Genehmigungen direkt konsumieren.
> 
> **Suchen Sie die SharePoint-/Teams-Referenzimplementierung?** Siehe [`SETUP-GUIDE.de-DE.md`](https://docs.squeeze.one/link/1163) (Admin-Einrichtung) und [`SHAREPOINT-CONTRACT.de-DE.md`](https://docs.squeeze.one/link/1165) (SharePoint-Listen-Vertrag).

---

### Übersicht

Die API für externe Genehmigungen macht ausstehende Genehmigungseinträge direkt als OData-Ressourcen innerhalb des Standard-API-V2-Frameworks von Business Central verfügbar. Jedes System, das HTTPS-Aufrufe machen kann — ein benutzerdefiniertes Portal, eine mobile App, ein Drittanbieter-Benachrichtigungsdienst, eine Azure Function — kann:

- Genehmigungsanfragen abfragen, die bestimmten Personen oder Gruppen zugewiesen sind
- Den Datensatzkontext dem Genehmiger anzeigen
- Genehmigungs- / Ablehnungsentscheidungen mit einem optionalen Kommentar übermitteln
- Gruppenbasierte (übernahmebasierte) Genehmigungen verwalten

**Kein SharePoint-Setup erforderlich.** Die API funktioniert unabhängig vom SharePoint-Integrations-Toggle (`SP Approvals Enabled`). Solange die DEXPRO-AWF-Erweiterung installiert und externe Genehmiger in BC konfiguriert sind, sind die API-Seiten aktiv.

#### Wann die API vs. die SharePoint-Integration verwenden

<table id="bkmrk-api-integrationshare"><thead><tr><th></th><th>API-Integration</th><th>SharePoint-Integration</th></tr></thead><tbody><tr><td>**Einrichtung**</td><td>Keine außer BC-API-Zugang</td><td>App-Registrierung, SP-Website, Assistent</td></tr><tr><td>**Benachrichtigung**</td><td>Ihr System übernimmt dies</td><td>Power Automate → Teams Adaptive Card</td></tr><tr><td>**Antworterfassung**</td><td>Ihre UI ruft die BC-API direkt auf</td><td>Genehmiger antwortet in Teams; PA schreibt in SP; BC fragt SP ab</td></tr><tr><td>**Lizenz (Genehmiger)**</td><td>Kostenpflichtige BC-Benutzerlizenz (siehe Lizenzhinweis)</td><td>Kostenpflichtige BC-Benutzerlizenz, plus M365 für Teams (siehe Lizenzhinweis)</td></tr><tr><td>**Geeignet für**</td><td>Benutzerdefinierte Portale, mobile Apps, Lieferanten-Self-Service</td><td>Schnelles Teams-Rollout, minimaler Entwicklungsaufwand</td></tr></tbody></table>

> **Lizenzierung — Verantwortung des Kunden/Partners:** Das Bearbeiten von Business-Central-Daten — ob direkt über die API oder indirekt über die SharePoint-Weiterleitung — erfordert für jeden Genehmiger eine **geeignete kostenpflichtige Business-Central-Benutzerlizenz**. Eine Microsoft-365-Lizenz allein gewährt **keinen** API- oder Schreibzugriff auf BC, und das Weiterleiten von Aktionen über ein Dienstkonto hebt die Lizenzpflicht pro Benutzer **nicht** auf (Microsoft-Multiplexing- bzw. Regeln zum indirekten Zugriff). Eine **Team-Member**-Lizenz kann für reine Genehmigungsnutzung ausreichen, jedoch beschränkt Microsoft Team Member auf vorgesehene Szenarien — für benutzerdefinierte oder Drittanbieter-Oberflächen ist dies **nicht garantiert**, und ein vollwertiger **Essentials/Premium**-Benutzer kann erforderlich sein. **DEXPRO gibt keine lizenzrechtliche Zusicherung.** Der Kunde und sein Microsoft-Lizenzierungspartner sind allein für die Ermittlung und Einhaltung der korrekten Lizenzierung verantwortlich; prüfen Sie den aktuellen [Microsoft Dynamics 365 Licensing Guide](https://go.microsoft.com/fwlink/?LinkId=866544).

---

### Voraussetzungen

<table id="bkmrk-anforderungdetailsbu"><thead><tr><th>Anforderung</th><th>Details</th></tr></thead><tbody><tr><td>**Business Central**</td><td>DEXPRO-AWF-Erweiterung installiert, BC 25 oder höher</td></tr><tr><td>**Entra-App-Registrierung**</td><td>Eine Entra-ID-App mit `Financials.ReadWrite.All` (oder ein delegierter Benutzerflow) für den BC-API-Zugang</td></tr><tr><td>**BC-Berechtigungssatz**</td><td>Das Dienstkonto des API-Aufrufers benötigt einen Berechtigungssatz, der Lesezugriff auf `DXP AWF Ext. Approval Entry` und Schreibzugriff für die gebundenen Aktionen (`approve`, `reject`, `claim`, `releaseClaim`, `markNotified`, `setTeamsMessageId`) gewährt. Der Berechtigungssatz **DXP AWF Admin** deckt dies ab.</td></tr><tr><td>**Externe Genehmiger**</td><td>Mindestens ein externer Genehmiger in BC konfiguriert (AWF-Einrichtung → Externe Genehmiger)</td></tr></tbody></table>

---

### Authentifizierung

Die BC-API-V2 verwendet OAuth 2.0 mit Microsoft Entra ID. Beziehen Sie ein Bearer-Token von:

```
POST https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token

grant_type=client_credentials
&client_id={clientId}
&client_secret={clientSecret}
&scope=https://api.businesscentral.dynamics.com/.default
```

Fügen Sie das Token in jeder Anfrage ein:

```
Authorization: Bearer {access_token}
```

> **Hinweis:** Der Client-Credentials-Flow eignet sich für Backend-Service-Integrationen. Für benutzer-delegierte Flows (z. B. ein Webportal, bei dem sich jeder Genehmiger selbst authentifiziert), verwenden Sie stattdessen Authorization Code + PKCE — das Token trägt dann die Benutzeridentität, was für Prüfzwecke nützlich ist.

---

### Basis-URL

```
https://api.businesscentral.dynamics.com/v2.0/{tenantId}/{environmentName}/api/dexpro/advancedWorkflow/v1.0
```

Alle Beispiele unten verwenden `{baseUrl}` als Kurzform für diese vollständige Basis-URL.

So finden Sie Ihre Unternehmens-ID (eine GUID, die in den meisten Anfragen erforderlich ist):

```
GET {baseUrl}/companies
```

Antwort (gekürzt):

```
{
  "value": [
    {
      "id": "5e9f4c3a-0001-ef11-9f8a-6045bd028c9f",
      "name": "CRONUS International Ltd.",
      ...
    }
  ]
}
```

Verwenden Sie den `id`-Wert als `{companyId}` in nachfolgenden Anfragen.

---

### Entitäten

Es stehen drei schreibgeschützte Entitätenmengen zur Verfügung. Alle verwenden **`SystemId`** (eine plattformzugewiesene GUID, als `id` exponiert) als OData-Schlüssel — der BC-API-V2-Standardkonvention folgend.

<table id="bkmrk-entit%C3%A4tenmengeodata-"><thead><tr><th>Entitätenmenge</th><th>OData-URL-Segment</th><th>OData-Schlüsselfeld</th><th>Beschreibung</th></tr></thead><tbody><tr><td>Genehmigungseinträge</td><td>`externalApprovalEntries`</td><td>`id` (SystemId)</td><td>Die aktionsfähigen Einträge — einer pro Genehmiger pro Genehmigungsanfrage. Hauptentität für Integrationen.</td></tr><tr><td>Genehmiger</td><td>`externalApprovers`</td><td>`id` (SystemId)</td><td>Die registrierten externen Genehmigerdatensätze. Nützlich für Bootstrap / Vorabfüllung.</td></tr><tr><td>Gruppenmitglieder</td><td>`externalApproverMembers`</td><td>`id` (SystemId)</td><td>Mitglieder externer Genehmigergruppen.</td></tr><tr><td>Eintragsdokument</td><td>`externalApprovalEntryDocuments`</td><td>`id` (SystemId)</td><td>Quelldokument-Datensatz als JSON. Separater Endpunkt — nur auf Anfrage abgerufen.</td></tr><tr><td>Eintragsanhänge</td><td>`externalApprovalEntryAttachments`</td><td>`id` (SystemId)</td><td>Standard-BC-Dokumentanhänge für den Quelldatensatz. Separater Endpunkt — nur auf Anfrage abgerufen.</td></tr></tbody></table>

> **Warum GUIDs, keine Integer?** Das `entryNo`-Integer-Feld ist im Antwort-Body für Anzeige und Korrelation noch vorhanden, ist aber nicht der OData-Schlüssel. Die Verwendung von SystemId (GUID) als Schlüssel folgt Microsofts offiziellen BC-API-Richtlinien und stellt die Kompatibilität mit Power Automate, Power Apps und Logic Apps Connectors sicher. Verwenden Sie immer `id` in URL-Pfaden.

---

### Mit Genehmigungseinträgen arbeiten

#### Alle ausstehenden Einträge für einen Genehmiger auflisten

```
GET {baseUrl}/companies({companyId})/externalApprovalEntries
    ?$filter=approverEmail eq 'hans.mueller@contoso.com' and status eq 'Pending'
    &$orderby=createdDateTime asc
```

Antwort:

```
{
  "@odata.context": "...",
  "value": [
    {
      "id": "b9f3a2c1-0001-ef11-bf8d-6045bd028c9f",
      "entryNo": 42,
      "approvalEntryNo": 17,
      "workflowInstanceId": "a1b2c3d4-0000-0000-0000-000000000001",
      "templateCode": "EINKAUFSGENEHMIGUNG",
      "stageCode": "EXT-PRUEFUNG",
      "stageDescription": "Externe Prüfung",
      "documentNo": "EK-00123",
      "documentType": "Order",
      "documentTableId": 38,
      "approverEmail": "hans.mueller@contoso.com",
      "approverDisplayName": "Hans Müller",
      "status": "Pending",
      "isGroupApproval": false,
      "isRelatedApproval": false,
      "description": "Fabrikam GmbH",
      "recordDescription": "Einkaufsbestellung EK-00123",
      "amount": 15000.00,
      "dueDate": "2026-05-15",
      "senderUserId": "EINKAUF",
      "languageCode": "DEU",
      "processed": false,
      "createdDateTime": "2026-05-04T09:12:33Z",
      "errorMessage": "",
      "spListItemId": "",
      "spAttachmentsUrl": ""
    }
  ]
}
```

#### Einzelnen Eintrag abrufen

```
GET {baseUrl}/companies({companyId})/externalApprovalEntries(b9f3a2c1-0001-ef11-bf8d-6045bd028c9f)
```

#### Häufige Filtermuster

```
# Alle ausstehenden Einträge für einen Genehmiger (Einzel + Gruppe)
$filter=approverEmail eq 'anna@contoso.com' and processed eq false and status ne 'Cancelled'

# Alle ausstehenden Einträge für eine Gruppe (allen Mitgliedern anzeigen, bevor eines übernimmt)
$filter=extApproverGroupCode eq 'FINANZ-GRUPPE' and status eq 'Pending'

# Einträge mit Verarbeitungsfehlern (zur Admin-Überwachung)
$filter=errorMessage ne '' and processed eq false

# Alle aktionsfähigen Einträge für ein Gruppenmitglied (ausstehend oder von ihm übernommen)
$filter=approverEmail eq 'anna@contoso.com'
    and (status eq 'Pending' or status eq 'Notified' or status eq 'Claimed')
    and processed eq false
```

---

### Gebundene Aktionen

Aktionen werden als OData-gebundene Aktionen über `POST` aufgerufen. Das URL-Muster ist:

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries({id})/Microsoft.NAV.{aktionsname}
Content-Type: application/json
```

`{id}` ist das `id`-Feld (SystemId-GUID) aus dem Eintrag. Ein erfolgreicher Aufruf gibt `200 OK` mit dem aktualisierten Eintrag im Antwort-Body zurück.

#### approve (Genehmigen)

Zeichnet eine Genehmigungsentscheidung auf und bringt den BC-Workflow voran.

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries(b9f3a2c1-0001-ef11-bf8d-6045bd028c9f)/Microsoft.NAV.approve
Content-Type: application/json

{
  "approverEmail": "hans.mueller@contoso.com",
  "comment": "Sieht gut aus. Im Rahmen des Q2-Budgets genehmigt."
}
```

`approverEmail` ist **erforderlich** — übergeben Sie die E-Mail des Benutzers, der die Genehmigung tatsächlich durchgeführt hat. Dies wird im BC-Protokoll und im Genehmigungseintrag aufgezeichnet. Da die API typischerweise von einem Backend-Dienst (Clientanmeldeinformationen) aufgerufen wird, hat BC keine andere Möglichkeit, die echte Identität des Akteurs zu kennen.

`comment` ist optional. Weglassen oder `""` übergeben, um ohne Kommentar zu genehmigen.

Antwort `200 OK`:

```
{
  "id": "b9f3a2c1-0001-ef11-bf8d-6045bd028c9f",
  "entryNo": 42,
  "approverEmail": "hans.mueller@contoso.com",
  "status": "Approved",
  "responseComment": "Sieht gut aus. Im Rahmen des Q2-Budgets genehmigt.",
  "responseDateTime": "2026-05-04T10:35:12Z",
  "processed": true,
  "processedDateTime": "2026-05-04T10:35:12Z",
  ...
}
```

#### reject (Ablehnen)

Zeichnet eine Ablehnungsentscheidung auf und steuert den BC-Ablehnungsflow (einschließlich Neuzuweisung für verknüpfte Genehmigungen).

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries(b9f3a2c1-0001-ef11-bf8d-6045bd028c9f)/Microsoft.NAV.reject
Content-Type: application/json

{
  "approverEmail": "hans.mueller@contoso.com",
  "comment": "Betrag überschreitet Abteilungsvollmacht. An CFO eskalieren."
}
```

`approverEmail` ist aus denselben Gründen wie bei `approve` **erforderlich**.

Antwort `200 OK`:

```
{
  "id": "b9f3a2c1-0001-ef11-bf8d-6045bd028c9f",
  "entryNo": 42,
  "approverEmail": "hans.mueller@contoso.com",
  "status": "Rejected",
  "responseComment": "Betrag überschreitet Abteilungsvollmacht. An CFO eskalieren.",
  "responseDateTime": "2026-05-04T10:38:44Z",
  "processed": true,
  ...
}
```

#### markNotified (Als benachrichtigt markieren)

Rufen Sie dies auf, nachdem Ihr System die Genehmigungsbenachrichtigung erfolgreich an den Genehmiger zugestellt hat (z. B. eine E-Mail gesendet oder die Karte in Ihrem Portal angezeigt hat). Wechselt den Status von `Pending` → `Notified`. Dies ist optional, aber empfohlen — es ermöglicht BC-Administratoren, zwischen Einträgen zu unterscheiden, die nie zugestellt wurden, und Einträgen, die auf eine Antwort warten.

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries(b9f3a2c1-0001-ef11-bf8d-6045bd028c9f)/Microsoft.NAV.markNotified
Content-Type: application/json

{}
```

Antwort `200 OK`:

```
{
  "id": "b9f3a2c1-0001-ef11-bf8d-6045bd028c9f",
  "entryNo": 42,
  "status": "Notified",
  ...
}
```

> Nur wirksam, wenn der aktuelle Status `Pending` ist. No-Op bei jedem anderen Status.

#### setTeamsMessageId (Teams-Message-ID setzen)

Rufen Sie dies **anstelle von `markNotified`** auf, wenn Ihr Transport die geposteten Nachricht später aktualisieren/schließen können muss (z. B. eine Microsoft-Teams-Adaptive-Card). Es tut alles, was `markNotified` tut (Pending → Notified), **und** speichert die Message-ID des Transports auf dem Eintrag (BC-Feld `Teams Message ID`) und spiegelt sie in die SharePoint-Spalte `TeamsMessageId`. Der Power-Automate-v2-Flow nutzt dies, damit eine Peer-Übernahme/-Entscheidung die noch offenen Karten anderer Gruppenmitglieder über *Adaptive Card aktualisieren* ersetzen (schließen) kann.

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries(b9f3a2c1-0001-ef11-bf8d-6045bd028c9f)/Microsoft.NAV.setTeamsMessageId
Content-Type: application/json

{ "teamsMessageId": "1700000000000" }
```

Antwort `200 OK`:

```
{
  "id": "b9f3a2c1-0001-ef11-bf8d-6045bd028c9f",
  "entryNo": 42,
  "status": "Notified",
  "teamsMessageId": "1700000000000",
  ...
}
```

> Übergeben Sie die Message-ID, die Ihr Transport beim Posten der Nachricht zurückgibt — bei der *Karte in einem Chat oder Kanal posten*-Aktion des Teams-Connectors ist das `body/id`. Leere Eingabe wird ignoriert (der Eintrag wird trotzdem als `Notified` markiert). Erneuter Aufruf mit derselben ID ist ein No-Op.

#### claim (Übernehmen) — *nur Gruppengenehmigungs-Anfragen*

Übernimmt einen Gruppengenehmigungs-Eintrag. Dies ist der „Erster gewinnt"-Schritt: Sobald übernommen, werden alle Einträge der anderen Gruppenmitglieder für dieselbe Genehmigungsanfrage storniert, und nur der Übernehmer kann dann genehmigen oder ablehnen.

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries(c4e7d1a2-0002-ef11-bf8d-6045bd028c9f)/Microsoft.NAV.claim
Content-Type: application/json

{
  "claimerEmail": "anna.schmidt@contoso.com"
}
```

`claimerEmail` ist **erforderlich** — übergeben Sie die E-Mail des Benutzers, der den Eintrag übernimmt. Dies wird im Prüfprotokoll aufgezeichnet und in den BC-Genehmigungseintrag als Identität des Übernehmers geschrieben.

Antwort `200 OK`:

```
{
  "id": "c4e7d1a2-0002-ef11-bf8d-6045bd028c9f",
  "entryNo": 43,
  "status": "Claimed",
  "claimedByEmail": "anna.schmidt@contoso.com",
  "claimedByDisplayName": "Anna Schmidt",
  "claimedDateTime": "2026-05-04T10:41:02Z",
  ...
}
```

> **Race Condition:** Wenn zwei Gruppenmitglieder gleichzeitig `claim` aufrufen, gewinnt nur eines. Der Verlierer erhält `400 Bad Request` mit dem Fehler-Body: *„Dieser Eintrag wurde bereits von anna.schmidt@contoso.com übernommen."* Ihre UI sollte dies abfangen und den Eintrag für den verlierenden Aufrufer aktualisieren.

#### releaseClaim (Freigeben) — *nur Gruppengenehmigungs-Anfragen*

Gibt einen zuvor übernommenen Eintrag frei und setzt ihn auf den Status `Notified` zurück, sodass ein anderes Gruppenmitglied ihn übernehmen kann.

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries(c4e7d1a2-0002-ef11-bf8d-6045bd028c9f)/Microsoft.NAV.releaseClaim
Content-Type: application/json

{}
```

Antwort `200 OK`:

```
{
  "id": "c4e7d1a2-0002-ef11-bf8d-6045bd028c9f",
  "entryNo": 43,
  "status": "Notified",
  "claimedByEmail": "",
  "claimedByDisplayName": "",
  ...
}
```

---

### Status-Zustandsautomat

```
                   ┌───────────────────────────────────┐
                   │                                   │
     BC erstellt   ▼                                   │
  Eintrag ──────► Pending ──── markNotified ──────► Notified
                   │                                   │
                   │           (nur Gruppe)            │
                   └──────────────────── ──────────────┘
                                        │
                                        ▼ claim
                                     Claimed ◄──── releaseClaim ──┐
                                        │                          │
                                        └──────────────────────────┘
                   │                    │
      approve ─────┤                    │ approve / reject
      reject ──────┤                    │
                   ▼                    ▼
               Approved / Rejected  (gleich)
                   │
              [processed = false, BC führt Workflow-Engine aus]
                   │
              [processed = true]
                   │
               ────┘
```

<table id="bkmrk-statusbedeutungaktio"><thead><tr><th>Status</th><th>Bedeutung</th><th>Aktionsfähig?</th></tr></thead><tbody><tr><td>`Pending`</td><td>Erstellt, noch nicht an den Genehmiger zugestellt</td><td>`markNotified`, `approve`, `reject`, `claim`</td></tr><tr><td>`Notified`</td><td>Ihr System hat bestätigt, dass der Genehmiger benachrichtigt wurde</td><td>`approve`, `reject`, `claim`</td></tr><tr><td>`Claimed`</td><td>Ein Gruppenmitglied hat diesen Eintrag übernommen</td><td>`approve`, `reject` (nur Übernehmer), `releaseClaim`</td></tr><tr><td>`Approved`</td><td>Genehmiger hat genehmigt; BC verarbeitet möglicherweise noch</td><td>—</td></tr><tr><td>`Rejected`</td><td>Genehmiger hat abgelehnt</td><td>—</td></tr><tr><td>`Cancelled`</td><td>Von BC storniert (Peer hat übernommen/entschieden, Workflow wurde neu zugewiesen, oder Dokument wurde wieder geöffnet)</td><td>—</td></tr></tbody></table>

> **`processed` vs. `status`:** `status` ist die Entscheidung des Genehmigers. `processed = true` bedeutet, dass die Workflow-Engine von BC die Entscheidung erfolgreich konsumiert hat. Ein Eintrag kann `status = Approved` mit `processed = false` haben, wenn der Workflow-Engine-Aufruf fehlgeschlagen ist — prüfen Sie `errorMessage` für Details. BC-Administratoren können von der Seite „Externe Genehmigungseinträge" aus erneut versuchen.

---

### Gruppengenehmigungs-Anfragen

Wenn eine Genehmigungsstufe auf eine **Externe Genehmigergruppe** abzielt, erstellt BC einen `externalApprovalEntry` für jedes Gruppenmitglied. Sie teilen dieselbe `workflowInstanceId` und `approvalEntryNo`, haben aber unterschiedliche `id`-, `approverEmail`- und `externalApproverCode`-Werte.

`isGroupApproval = true` gilt für alle.

#### Integrationsflow für Gruppengenehmigungs-Anfragen

1. **Alle Mitglieder benachrichtigen** — Einträge nach `extApproverGroupCode` + `status eq 'Pending'` abfragen. Jedem Mitglied seinen eigenen Eintrag anzeigen und die `id` des Eintrags für nachfolgende Aktionsaufrufe speichern.
2. **Mitglied übernimmt** — Wenn ein Mitglied auf „Übernehmen" tippt, rufen Sie `Microsoft.NAV.claim` auf der `id` **seines** Eintrags auf. BC storniert die Einträge der anderen Mitglieder automatisch.
3. **Übernehmer entscheidet** — Zeigen Sie dem Genehmiger seinen übernommenen Eintrag. Er ruft `approve` oder `reject` auf derselben `id` auf, die er übernommen hat.
4. **Auf Stornierungen prüfen** — Nach einer Übernahme wechseln die Einträge anderer Gruppenmitglieder auf `Cancelled`. Ihre UI sollte damit elegant umgehen (z. B. „Diese Genehmigung wurde von Anna Schmidt übernommen").

#### Peer-Stornierungen erkennen

```
GET {baseUrl}/companies({companyId})/externalApprovalEntries
    ?$filter=approverEmail eq 'bob@contoso.com' and status eq 'Cancelled' and processed eq false
    &$orderby=createdDateTime desc
    &$top=50
```

Einträge im Status `Cancelled` mit `processed = false` wurden storniert, weil ein Peer zuerst übernommen oder entschieden hat (nicht weil der Workflow von einem BC-Benutzer widerrufen wurde).

#### Gruppen mit einem Mitglied

BC übernimmt Einträge für Gruppen mit einem Mitglied automatisch bei der Erstellung — `status` ist beim ersten Abruf bereits `Claimed` und `claimedByEmail` ist vorausgefüllt. Überspringen Sie den Übernahmeschritt und gehen Sie direkt zu `approve` / `reject`.

---

### Feldreferenz — externalApprovalEntry

<table id="bkmrk-json-feldtypbeschrei"><thead><tr><th>JSON-Feld</th><th>Typ</th><th>Beschreibung</th></tr></thead><tbody><tr><td>`id`</td><td>GUID</td><td>**OData-Schlüssel.** SystemId — verwenden Sie diesen in allen Aktions-URLs und Einzeldatensatz-GETs.</td></tr><tr><td>`entryNo`</td><td>Integer</td><td>Interne BC-Sequenznummer. Nützlich für Anzeige, Korrelation und Lesen verwandter SharePoint-Elemente (`BCEntryNo`-Spalte). Nicht der OData-Schlüssel.</td></tr><tr><td>`approvalEntryNo`</td><td>Integer</td><td>Schlüssel des verknüpften BC-Standard-Genehmigungspostens.</td></tr><tr><td>`workflowInstanceId`</td><td>GUID</td><td>BC-Workflow-Instanz. Von allen Einträgen (inkl. verknüpfter Zeilen) in derselben Workflow-Ausführung geteilt.</td></tr><tr><td>`templateCode`</td><td>String</td><td>AWF-Workflowvorlagen-Code.</td></tr><tr><td>`stageCode`</td><td>String</td><td>AWF-Workflow-Schritt-Code.</td></tr><tr><td>`stageDescription`</td><td>String</td><td>Für Menschen lesbare Schrittbeschreibung. Dem Genehmiger anzeigen.</td></tr><tr><td>`documentTableId`</td><td>Integer</td><td>BC-Tabellen-ID des Quelldatensatzes (z. B. `38` für Einkaufskopf, `36` für Verkaufskopf). Nützlich für Routing oder tabellenspezifische UI in Ihrem Portal.</td></tr><tr><td>`documentNo`</td><td>String</td><td>Dokumentnummer, die genehmigt wird (z. B. `EK-00123`).</td></tr><tr><td>`documentType`</td><td>String</td><td>Beschriftung des Dokumenttyp-Enums (z. B. `Order`, `Invoice`, `""` für benutzerdefinierte Tabellen).</td></tr><tr><td>`externalApproverCode`</td><td>String</td><td>Code des externen Genehmigerdatensatzes in BC.</td></tr><tr><td>`extApproverGroupCode`</td><td>String</td><td>Code der externen Genehmigergruppe, falls dies eine Gruppengenehmigungs-Anfrage ist. Leer bei Einzelgenehmigungen.</td></tr><tr><td>`approverEmail`</td><td>String</td><td>E-Mail des diesem spezifischen Eintrag zugewiesenen Genehmigers. Für Routing verwenden.</td></tr><tr><td>`approverDisplayName`</td><td>String</td><td>Anzeigename des zugewiesenen Genehmigers.</td></tr><tr><td>`status`</td><td>String</td><td>Siehe [Status-Zustandsautomat](#bkmrk-status-zustandsautom) oben.</td></tr><tr><td>`isGroupApproval`</td><td>Boolesch</td><td>Ob dies Teil einer übernahmebasierten Gruppengenehmigungs-Anfrage ist.</td></tr><tr><td>`claimedByEmail`</td><td>String</td><td>E-Mail des Gruppenmitglieds, das den Eintrag übernommen hat. Leer bei Einzelgenehmigungen.</td></tr><tr><td>`claimedByDisplayName`</td><td>String</td><td>Anzeigename des Übernehmers.</td></tr><tr><td>`claimedDateTime`</td><td>Datum/Uhrzeit</td><td>Wann der Eintrag übernommen wurde (UTC).</td></tr><tr><td>`processed`</td><td>Boolesch</td><td>Ob die Workflow-Engine von BC diese Entscheidung erfolgreich konsumiert hat.</td></tr><tr><td>`processedDateTime`</td><td>Datum/Uhrzeit</td><td>Wann BC die Entscheidung verarbeitet hat (UTC).</td></tr><tr><td>`errorMessage`</td><td>String</td><td>Nicht leer, wenn `processed = false` nach einer Genehmigungs-/Ablehnungsentscheidung — der Workflow-Engine-Aufruf ist fehlgeschlagen.</td></tr><tr><td>`description`</td><td>String</td><td>Kontextbezogene Beschreibung (z. B. Kreditorenname, Debitorenname). Gut für die Anzeige in einer Karte oder Liste.</td></tr><tr><td>`recordDescription`</td><td>String</td><td>Vollständige Datensatzkennung (z. B. `"Tabelle 38 (Einkaufskopf): Bestellung, EK-00123"`). Für detaillierte Anzeige verwenden.</td></tr><tr><td>`amount`</td><td>Dezimalzahl</td><td>Genehmigungsbetrag.</td></tr><tr><td>`dueDate`</td><td>Datum</td><td>Fälligkeitsdatum der Genehmigung (ISO-8601-Datum, z. B. `2026-05-15`).</td></tr><tr><td>`senderUserId`</td><td>String</td><td>BC-Benutzer-ID der Person, die das Dokument zur Genehmigung gesendet hat.</td></tr><tr><td>`languageCode`</td><td>String</td><td>BC-Sprachcode des zugewiesenen Genehmigers (z. B. `ENU`, `DEU`). Für die Lokalisierung Ihrer Benachrichtigung verwenden.</td></tr><tr><td>`responseComment`</td><td>String</td><td>Vom Genehmiger eingegebener Kommentar. Nach Genehmigung/Ablehnung befüllt.</td></tr><tr><td>`responseDateTime`</td><td>Datum/Uhrzeit</td><td>Wann der Genehmiger geantwortet hat (UTC).</td></tr><tr><td>`isRelatedApproval`</td><td>Boolesch</td><td>Wahr für zeilenebene (verknüpfte) Einträge, die mit einer Kopfgenehmigung verknüpft sind.</td></tr><tr><td>`createdDateTime`</td><td>Datum/Uhrzeit</td><td>Wann BC diesen Eintrag erstellt hat (UTC).</td></tr><tr><td>`spListItemId`</td><td>String</td><td>SharePoint-Listenelement-ID. Nur befüllt, wenn SP-Genehmigungen aktiviert sind. Für reine API-Integrationen ignorieren.</td></tr><tr><td>`spAttachmentsUrl`</td><td>String</td><td>SharePoint-Freigabelink für Dokumentanhänge. Nur befüllt, wenn SP-Genehmigungen aktiviert sind und Anhänge hochgeladen wurden.</td></tr></tbody></table>

---

### Feldreferenz — externalApprover

<table id="bkmrk-json-feldtypbeschrei-1"><thead><tr><th>JSON-Feld</th><th>Typ</th><th>Beschreibung</th></tr></thead><tbody><tr><td>`id`</td><td>GUID</td><td>**OData-Schlüssel.** SystemId.</td></tr><tr><td>`code`</td><td>String</td><td>Interner BC-Genehmiger-Code (Code\[20\]).</td></tr><tr><td>`displayName`</td><td>String</td><td>Vollständiger Name.</td></tr><tr><td>`email`</td><td>String</td><td>E-Mail / UPN.</td></tr><tr><td>`languageCode`</td><td>String</td><td>BC-Sprachcode für Benachrichtigungen.</td></tr><tr><td>`entraObjectId`</td><td>GUID</td><td>Entra-ID-Objekt-ID (befüllt für aus Entra synchronisierte Genehmiger).</td></tr><tr><td>`blocked`</td><td>Boolesch</td><td>Blockierte Genehmiger können keine neuen Einträge erhalten.</td></tr></tbody></table>

---

### Feldreferenz — externalApproverMember

<table id="bkmrk-json-feldtypbeschrei-2"><thead><tr><th>JSON-Feld</th><th>Typ</th><th>Beschreibung</th></tr></thead><tbody><tr><td>`id`</td><td>GUID</td><td>**OData-Schlüssel.** SystemId.</td></tr><tr><td>`groupCode`</td><td>String</td><td>Der Code der externen Genehmigergruppe.</td></tr><tr><td>`lineNo`</td><td>Integer</td><td>Zeilennummer innerhalb der Gruppe (nur informativ).</td></tr><tr><td>`externalApproverCode`</td><td>String</td><td>Genehmiger-Code des Mitglieds.</td></tr><tr><td>`displayName`</td><td>String</td><td>Anzeigename des Mitglieds.</td></tr><tr><td>`email`</td><td>String</td><td>E-Mail des Mitglieds.</td></tr></tbody></table>

---

### Vollständiges Beispiel: Einzelner Genehmigungsflow

Die folgende Sequenz implementiert ein minimales Genehmigungsportal für Einzelgenehmiger.

#### Schritt 1 — Authentifizieren und ausstehende Einträge abrufen

```
GET {baseUrl}/companies({companyId})/externalApprovalEntries
    ?$filter=approverEmail eq 'hans.mueller@contoso.com'
        and status ne 'Cancelled'
        and processed eq false
    &$select=id,entryNo,documentNo,description,stageDescription,amount,dueDate,status,isGroupApproval,createdDateTime
    &$orderby=createdDateTime asc
```

#### Schritt 2 — Eintragsdetails dem Genehmiger anzeigen

Verwenden Sie `description`, `stageDescription`, `amount`, `dueDate` und `senderUserId`, um eine Genehmigungskarte aufzubauen. Speichern Sie die `id`-GUID des Eintrags — Sie benötigen sie für Aktionsaufrufe.

#### Schritt 3 — Als benachrichtigt markieren

Nachdem die Karte dem Genehmiger angezeigt wurde:

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries(b9f3a2c1-0001-ef11-bf8d-6045bd028c9f)/Microsoft.NAV.markNotified
Content-Type: application/json

{}
```

#### Schritt 4 — Entscheidung erfassen

Genehmiger klickt auf Genehmigen mit einem Kommentar:

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries(b9f3a2c1-0001-ef11-bf8d-6045bd028c9f)/Microsoft.NAV.approve
Content-Type: application/json

{
  "approverEmail": "hans.mueller@contoso.com",
  "comment": "Im Rahmen des Budgets, genehmigt."
}
```

---

### Vollständiges Beispiel: Gruppengenehmigungs-Flow

#### Schritt 1 — Alle Gruppenmitglieder benachrichtigen

Einträge für die Gruppe abrufen, einen pro Mitglied:

```
GET {baseUrl}/companies({companyId})/externalApprovalEntries
    ?$filter=extApproverGroupCode eq 'FINANZ-GRUPPE'
        and (status eq 'Pending' or status eq 'Notified')
        and processed eq false
```

Jedes Mitglied mit einer Benachrichtigung benachrichtigen, die die `id` seines Eintrags enthält.

#### Schritt 2 — Mitglied übernimmt

Anna öffnet das Portal und tippt auf „Übernehmen" (mit der `id` aus ihrem eigenen Eintrag):

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries(c4e7d1a2-0002-ef11-bf8d-6045bd028c9f)/Microsoft.NAV.claim
Content-Type: application/json

{
  "claimerEmail": "anna.schmidt@contoso.com"
}
```

BC storniert alle Geschwistereinträge (die Einträge der anderen Gruppenmitglieder für dieselbe Genehmigung).

**Race Condition behandeln:** Wenn der Eintrag bereits von jemand anderem übernommen wurde, gibt BC `400 Bad Request` zurück:

```
{
  "error": {
    "code": "Internal",
    "message": "Dieser Eintrag wurde bereits von bob.mueller@contoso.com übernommen. CorrelationId: ..."
  }
}
```

Aktualisieren und „Diese Genehmigung wurde von Bob Müller übernommen" für Anna anzeigen.

#### Schritt 3 — Übernehmer entscheidet

Anna ruft genehmigen mit derselben `id` auf:

```
POST {baseUrl}/companies({companyId})/externalApprovalEntries(c4e7d1a2-0002-ef11-bf8d-6045bd028c9f)/Microsoft.NAV.approve
Content-Type: application/json

{
  "approverEmail": "anna.schmidt@contoso.com",
  "comment": "Zeilenpositionen geprüft, alle richtlinienkonform."
}
```

---

### Fehlerbehandlung

#### HTTP-Statuscodes

<table id="bkmrk-codewann200-okaktion"><thead><tr><th>Code</th><th>Wann</th></tr></thead><tbody><tr><td>`200 OK`</td><td>Aktion erfolgreich. Antwort-Body enthält den aktualisierten Eintrag.</td></tr><tr><td>`400 Bad Request`</td><td>Geschäftslogikfehler (Eintrag bereits verarbeitet, bereits von jemand anderem übernommen, nicht in einem übernahmbaren Zustand). `error.message` für Details lesen.</td></tr><tr><td>`401 Unauthorized`</td><td>Fehlendes oder abgelaufenes Bearer-Token.</td></tr><tr><td>`403 Forbidden`</td><td>Das BC-Benutzerkonto des API-Aufrufers fehlt der erforderliche Berechtigungssatz.</td></tr><tr><td>`404 Not Found`</td><td>Eintrag in diesem Unternehmen nicht gefunden, oder falsche `id`-GUID.</td></tr></tbody></table>

#### Idempotenz

- `approve` und `reject` sind gesichert: Sie auf einem bereits verarbeiteten Eintrag aufzurufen, gibt `400` mit *„Dieser externe Genehmigungseintrag wurde bereits verarbeitet…"* zurück. Sicher nur zu wiederholen, wenn der vorherige Aufruf eine Nicht-`200`-Antwort zurückgegeben hat.
- `markNotified` ist ein No-Op, wenn der Status nicht `Pending` ist — sicher mehrfach aufzurufen.
- `claim` ist absichtlich nicht idempotent: Es zweimal von zwei verschiedenen Aufrufern aufzurufen ist die Race Condition, die Sie behandeln müssen.

#### SharePoint/Teams-Race-Condition

Wenn die SharePoint-Integration aktiv ist, kann der Eintrag zwischen Ihrer letzten Abfrage und Ihrem `approve`/`reject`/`claim`-Aufruf über Teams entschieden worden sein. Die API synchronisiert den Live-SP-Zustand vor der Verarbeitung (`SyncFromSPIfNeeded`); ein inzwischen geschlossener Eintrag wird daher mit `400` und einer der tatsächlichen Guard-Meldungen abgelehnt:

> *„This external approval entry has already been processed by Business Central and cannot be acted on again."*
> 
> *„This external approval entry has been cancelled (a peer in the group decided first, or the workflow was reassigned). Refresh the list to see the current state."*

Dies ist kein wiederholbarer Fehler — behandeln Sie ihn wie einen `Cancelled`-Status und aktualisieren Sie die Eintrags-Liste für den Benutzer.

#### Einträge, die verschwinden

Der Status eines Eintrags kann jederzeit auf `Cancelled` wechseln aufgrund externer Ereignisse:

- Ein BC-Benutzer weist den zugrundeliegenden Genehmigungsposten neu zu oder storniert ihn
- Das Dokument wird wieder geöffnet (storniert den gesamten Workflow)
- Ein Peer in derselben Gruppe hat zuerst übernommen (nur bei Gruppeneinträgen)

Prüfen Sie immer auf den Status `Cancelled`, bevor Sie einem Genehmiger einen Eintrag anzeigen, und behandeln Sie `400`-Antworten elegant mit einer Aktualisierung.

---

### Abfrageempfehlungen

Es gibt keinen Webhook- oder Push-Mechanismus — Ihre Integration fragt die API ab. Empfohlene Intervalle:

<table id="bkmrk-anwendungsfallinterv"><thead><tr><th>Anwendungsfall</th><th>Intervall</th></tr></thead><tbody><tr><td>Benachrichtigungszustellung (neue `Pending`-Einträge abrufen)</td><td>1–5 Minuten</td></tr><tr><td>Genehmigungsportal-Aktualisierung (angemeldeter Benutzer)</td><td>Auf Anfrage (Benutzeraktualisierung) + beim Seitenaufruf</td></tr><tr><td>Gruppenmitgliedschaftsaktualisierung</td><td>`externalApproverMembers` bei jedem Portal-Login abfragen</td></tr></tbody></table>

Um neu erstellte Einträge seit der letzten Abfrage zu erkennen, filtern Sie auf `createdDateTime`:

```
GET {baseUrl}/companies({companyId})/externalApprovalEntries
    ?$filter=status eq 'Pending' and createdDateTime gt 2026-05-04T09:00:00Z
    &$orderby=createdDateTime asc
```

---

### Datensatz-JSON

Jeder Genehmigungseintrag hat einen entsprechenden `externalApprovalEntryDocuments`-Endpunkt, der die vollständige JSON-Darstellung des Quelldokument-Datensatzes zurückgibt — die eigentliche BC-Tabellenzeile, die genehmigt wird (z. B. die Einkaufsbestellung, das Verkaufsangebot oder der benutzerdefinierte Datensatz).

Dies ist ein **separater Endpunkt** von `externalApprovalEntries`. Er wird nur abgerufen, wenn explizit aufgerufen, damit Listenabfragen schnell bleiben.

```
GET {baseUrl}/companies({companyId})/externalApprovalEntryDocuments(b9f3a2c1-0001-ef11-bf8d-6045bd028c9f)
```

Die `id` ist dieselbe SystemId-GUID wie der entsprechende `externalApprovalEntry`. Beispielantwort:

```
{
  "id": "b9f3a2c1-0001-ef11-bf8d-6045bd028c9f",
  "entryNo": 42,
  "documentNo": "EK-00123",
  "recordJson": "{\"No_\":\"EK-00123\",\"Document_Type\":\"Order\",\"Buy-from_Vendor_No_\":\"K00010\",\"Buy-from_Vendor_Name\":\"Fabrikam GmbH\",\"Amount\":15000.00,\"Amount_Including_VAT\":17850.00,\"Due_Date\":\"2026-05-15\",\"TableNo\":38,\"TableName\":\"Purchase Header\",\"TableCaption\":\"Einkaufskopf\",\"Company\":\"CRONUS International Ltd.\", ...}"
}
```

> `recordJson` ist eine JSON-Zeichenfolge (kein verschachteltes Objekt) — mit `JSON.parse()` in Ihrem Client parsen.

#### Was das JSON enthält

Das JSON-Objekt wird von `DXP Json Helper.Rec2Json` erzeugt und enthält:

- **Alle Tabellenfelder** — Feldbeschriftungen (mit durch `_` ersetzten Leerzeichen) als Schlüssel, Werte in ihre JSON-nativen Typen serialisiert
- **Metadatenfelder**, die automatisch vom Helper hinzugefügt werden:
    - `TableNo` — numerische Tabellen-ID
    - `TableName` — interner Tabellenname
    - `TableCaption` — lokalisierte Tabellenbeschriftung
    - `Company` — Unternehmensname
    - `RecordId` — BC-RecordId-Zeichenfolge
    - `SysLink` — SystemId + TableNo-Komposit

#### Leistungshinweis

`recordJson` wird bei jedem Datensatzlesen berechnet — er navigiert zum Quelldokument und serialisiert alle seine Felder. Da er auf einem dedizierten Endpunkt liegt, wird er nur abgerufen, wenn Sie `externalApprovalEntryDocuments` explizit aufrufen. Verwenden Sie den Eintragsendpunkt für Listenabfragen und rufen Sie den Dokumentendpunkt nur auf, wenn Sie den vollständigen Datensatz benötigen:

```
# Schnelle Liste — kein Dokumentnachschlag
GET {baseUrl}/companies({companyId})/externalApprovalEntries
    ?$filter=approverEmail eq 'hans@contoso.com' and status eq 'Pending'

# Auf Anfrage — vollständigen Datensatz nur für den vom Genehmiger geöffneten Eintrag abrufen
GET {baseUrl}/companies({companyId})/externalApprovalEntryDocuments({id})
```

#### Fallback

Wenn das Quelldokument gelöscht wurde oder der Genehmigungspostens-Link fehlt, gibt `recordJson` `"{}"` (eine leere JSON-Objektzeichenfolge) statt eines Fehlers zurück.

---

### Datensatzanhänge

Jeder Genehmigungseintrag hat einen entsprechenden `externalApprovalEntryAttachments`-Endpunkt, der alle Standard-BC-Dokumentanhänge für den Quelldatensatz als JSON-Array zurückgibt. Jedes Element enthält die Dateimetadaten und den vollständigen Dateiinhalt als Base64-Zeichenfolge, bereit zur clientseitigen Darstellung oder zum Download.

```
GET {baseUrl}/companies({companyId})/externalApprovalEntryAttachments(b9f3a2c1-0001-ef11-bf8d-6045bd028c9f)
```

Beispielantwort:

```
{
  "id": "b9f3a2c1-0001-ef11-bf8d-6045bd028c9f",
  "entryNo": 42,
  "documentNo": "EK-00123",
  "recordAttachmentsJson": "[{\"fileName\":\"EK-00123 Spezifikation\",\"fileExtension\":\"pdf\",\"attachedDate\":\"2026-05-03T14:22:00Z\",\"attachedBy\":\"Alice Johnson\",\"lineNo\":0,\"contentBase64\":\"JVBERi0xLjQK...\"}]"
}
```

> `recordAttachmentsJson` ist eine JSON-Zeichenfolge — mit `JSON.parse()` parsen.

#### Anhang-Objektfelder

<table id="bkmrk-feldtypbeschreibungf"><thead><tr><th>Feld</th><th>Typ</th><th>Beschreibung</th></tr></thead><tbody><tr><td>`fileName`</td><td>String</td><td>Dateiname ohne Erweiterung.</td></tr><tr><td>`fileExtension`</td><td>String</td><td>Dateierweiterung ohne Punkt (z. B. `pdf`, `xlsx`, `png`).</td></tr><tr><td>`attachedDate`</td><td>Datum/Uhrzeit</td><td>Wann die Datei angehängt wurde (UTC).</td></tr><tr><td>`attachedBy`</td><td>String</td><td>BC-Benutzer-ID der Person, die die Datei angehängt hat.</td></tr><tr><td>`lineNo`</td><td>Integer</td><td>`0` für Anhänge auf Dokumentebene (Kopf); ungleich null für Anhänge auf Zeilenebene.</td></tr><tr><td>`contentBase64`</td><td>String</td><td>Vollständiger Dateiinhalt, Base64-kodiert. Leere Zeichenfolge, wenn die Datei keinen Inhalt hat. Mit `atob()` im Browser oder dem Base64-Decoder Ihrer Plattform dekodieren.</td></tr></tbody></table>

#### Clientseitiges Dekodierungsbeispiel (JavaScript)

```
const attachments = JSON.parse(entry.recordAttachmentsJson);
attachments.forEach(a => {
  const bytes = Uint8Array.from(atob(a.contentBase64), c => c.charCodeAt(0));
  const blob  = new Blob([bytes], { type: mimeTypeFor(a.fileExtension) });
  const url   = URL.createObjectURL(blob);
  // Download-Link rendern oder inline öffnen
});
```

---

### Verwandte Dokumentation

<table id="bkmrk-dokumentzwecksetup-g"><thead><tr><th>Dokument</th><th>Zweck</th></tr></thead><tbody><tr><td>[`SETUP-GUIDE.de-DE.md`](https://docs.squeeze.one/link/1163)</td><td>Admin-Einrichtungshandbuch für die SharePoint-/Teams-Integration</td></tr><tr><td>[`SHAREPOINT-CONTRACT.de-DE.md`](https://docs.squeeze.one/link/1165)</td><td>Vertrag für Integrationen, die die SharePoint-Liste direkt lesen/schreiben</td></tr><tr><td>[`POWERAUTOMATE-DEV-GUIDE.de-DE.md`](https://docs.squeeze.one/link/1164)</td><td>Internes Handbuch zur Pflege des Power-Automate-Referenzflows</td></tr></tbody></table>