# OpenAPI — generischer REST-Konnektor

> Generischer REST-Konnektor, der Endpunkte, Wrapper-Schlüssel und Datentypen direkt aus einer OpenAPI-Spezifikation ableitet.

---

Site [llms.txt](/llms.txt)

---

Der OpenAPI-Konnektor verbindet die DataBridge mit einer **beliebigen REST-API**, für die
eine OpenAPI-Spezifikation (JSON oder YAML) existiert. Anders als bei den übrigen HTTP-
Konnektoren werden Pfade, Wrapper-Schlüssel (`{"clients": [...]}` vs. `{"client": {...}}`)
und Feldtypen **nicht** pro Ressource fest verdrahtet, sondern zur Laufzeit aus der
Spezifikation aufgelöst. Ein Mapping muss dafür lediglich `SourceTable`/`TargetTable` auf
den Ressourcennamen setzen (z. B. `clients`).

| Eigenschaft | Wert |
|---|---|
| Lesen | ✅ unterstützt |
| Schreiben (Create/Update) | ✅ unterstützt |
| Löschen | ✅ unterstützt |
| Aktionsaufrufe (InvokeAction) | ✅ unterstützt |
| Job-Filter | ⚠️ wird als Query-String an die Liste-Anfrage angehängt (kein `$filter`-Standard, API-abhängig) |
| Status | **In Entwicklung / Preview** |

> **Status: in Entwicklung**
>
> Der Konnektor befindet sich noch in aktiver Entwicklung. Die OpenAPI-Spezifikation wird über den
> in `openApiSpecUrl` konfigurierten Endpunkt geladen. Bekannte Einschränkungen:
> 
> - Der Diagnose-Endpunkt `CheckList` meldet für OpenAPI-Mappings fälschlich „could not find table“.
>   Er vergleicht die Tabellennamen mit den Pfaden der Spezifikation (`/v1/customers`) statt mit
>   den Ressourcennamen (`customers`). Auf das Lesen und Schreiben hat das keinen Einfluss.
> - `get_connector_table_schema` weist keinen Primärschlüssel aus. Intern erkennt der Konnektor
>   den Schlüssel am Namen der Property (`id`, `Id`, `ID`, `uuid`, `key`).



---

## Verbindungsparameter

```json
{
  "baseUrl": "https://api.example.com",
  "openApiSpecUrl": "https://api.example.com/openapi.yaml",
  "clientId": "<Client-ID>",
  "clientSecret": "<Client-Secret>",
  "tenantId": "<Entra Tenant ID>",
  "scope": "<OAuth2-Scope>",
  "tokenUrl": "https://api.example.com/oauth/token",
  "apiKeyHeader": "X-API-Key",
  "apiKeyValue": "<API-Key>",
  "customHeaders": { "X-Custom-Header": "wert" },
  "nextLinkPath": "@odata.nextLink",
  "nextPagePath": "meta.next_page",
  "pageQueryParam": "page",
  "pageSizeQueryParam": "per_page",
  "updateMethod": "PATCH",
  "authenticateSpecRequest": false
}
```

| Parameter | Pflicht | Standardwert | Beschreibung |
|---|:---:|---|---|
| `baseUrl` | ✅ | — | Basis-URL der API (ohne abschließenden Schrägstrich; wird bei Bedarf entfernt) |
| `openApiSpecUrl` | ✅ | — | URL zur OpenAPI-Spezifikation (JSON oder YAML) |
| `authenticateSpecRequest` | — | `false` | `true` = die Spezifikation wird mit denselben Zugangsdaten wie die API abgerufen; `false` = ohne Authentifizierung |
| `customHeaders` | — | *(leer)* | Zusätzliche statische Header, die bei **jeder** Anfrage mitgeschickt werden (z. B. Mandanten- oder Staging-Header) |
| `nextLinkPath` | — | *(leer)* | JSON-Pfad zur nächsten Seiten-URL in paginierten Antworten (z. B. `@odata.nextLink`). Gesetzt ⇒ URL-basierte Paginierung. |
| `nextPagePath` | — | *(leer)* | JSON-Pfad zur nächsten Seitennummer (z. B. `meta.next_page`). Erfordert zusätzlich `pageQueryParam`. |
| `pageQueryParam` | — | *(leer)* | Query-Parameter für die Seitennummer (z. B. `page`), zusammen mit `nextPagePath` |
| `pageSizeQueryParam` | — | *(leer)* | Query-Parameter für die Seitengröße (z. B. `per_page`); wenn gesetzt, wird `maxPageSize` darüber mitgeschickt |
| `updateMethod` | — | `PATCH` | HTTP-Methode für Aktualisierungen. `PUT` für APIs, die volle Ersetzung statt Teilaktualisierung erwarten. |

Zusätzlich stehen die universellen Parameter `maxPageSize`, `language`,
`httpClientTimeoutSeconds`, `keyVaultUrl`, `logCreateRequests`, `logUpdateRequests` und
`logReceivedData` zur Verfügung — siehe [Verbindungen](../../konfiguration/verbindungen/).

> **Weder nextLinkPath noch nextPagePath gesetzt**
>
> Ohne eines der beiden Paginierungs-Felder liest der Konnektor **nur die erste Seite** der
> Liste-Antwort — es findet keine automatische Fortsetzung statt.



---

## Authentifizierung

Der OpenAPI-Konnektor nutzt dieselbe Auswahllogik wie die übrigen HTTP-Konnektoren, erweitert
um API-Key-Authentifizierung:

| Methode | Aktivierung | Parameter |
|---|---|---|
| **Basic Authentication** | `userName` + `password` gesetzt | `userName`, `password` |
| **OAuth2 Client Credentials** | `clientId` + `clientSecret` + `tenantId` gesetzt (Vorgabe, wenn nichts anderes zutrifft) | `clientId`, `clientSecret`, `tenantId`, `scope`, optional `tokenUrl` |
| **API Key** | `apiKeyHeader` gesetzt | `apiKeyHeader`, `apiKeyValue` |

```json
{
  "apiKeyHeader": "X-Qonto-Staging-Token",
  "apiKeyValue": "<Token>"
}
```

> **tokenUrl — konfigurierbarer OAuth2-Endpunkt**
>
> Bei den übrigen HTTP-Konnektoren (D365BC, D365CE, D365NAV) ist der OAuth2-Token-Endpunkt fest
> auf den Microsoft-Login-Endpunkt eingestellt. Der OpenAPI-Konnektor führt zusätzlich den
> Parameter `tokenUrl` ein: Ist er gesetzt, wird der Token dort statt beim
> Microsoft-Standardendpunkt angefragt. Damit lassen sich auch APIs anbinden, deren
> OAuth2-Provider nicht Microsoft Entra ID ist. Bleibt `tokenUrl` leer, greift weiterhin der
> Microsoft-Standardendpunkt.



Zusätzliche statische Header (z. B. mandantenspezifische Tokens, die parallel zu OAuth2 oder
API-Key benötigt werden) lassen sich unabhängig von der Authentifizierungsmethode über
`customHeaders` mitgeben.

---

## Tabellen- und Feldnamen

- **SourceTable / TargetTable:** kurzer Ressourcenname, wie er in der OpenAPI-Spezifikation
  als letztes Pfadsegment vorkommt (z. B. `clients` für `/v2/clients`). Der Konnektor sucht
  in der Spezifikation nach einem Pfad, der auf `/<Ressourcenname>` endet und **keine**
  Parameter enthält; ein zugehöriger Einzeldatensatz-Pfad (`/v2/clients/{id}`) wird
  automatisch mitaufgelöst.
- Alternativ kann auch der **vollständige Pfad** aus der Spezifikation eingetragen werden
  (Rückwärtskompatibilität).
- **SourceField / TargetField:** Feldname aus dem JSON-Schema der Ressource. Verschachtelte
  Felder werden mit Punktnotation adressiert (z. B. `phone.country_code`).

Liste- und Einzeldatensatz-Antworten müssen nicht manuell konfiguriert werden: Wrapper-
Schlüssel wie `{"clients": [...]}` oder `{"client": {...}}` werden aus der Spezifikation
erkannt. Fehlen entsprechende Hinweise in der Spezifikation, erkennt der Konnektor ersatzweise
die gebräuchlichen Muster `value`, `data` und `results` als Array-Wrapper.

---

## Unterstützte TargetKey-Typen

| Typ | Beschreibung |
|---|---|
| `PrimaryKey` / `Id` | Schlüsselwert wird direkt aus dem `TargetKey.KeyName`-Attribut der Nachricht gelesen und unverändert in die Item-URL eingesetzt |
| `CustomKey` | Ein oder mehrere Feld-Wert-Paare werden als Query-String (`key=wert&key2=wert2`) an die Liste-Anfrage angehängt; der Datensatz wird darüber gesucht |
| `Filter` | Wie `CustomKey`, zusätzlich wird `id` als besonderer `keyName`-Wert erkannt und dafür `message.Id` verwendet. Für Update und Löschen wird der Schlüssel des gefundenen Datensatzes verwendet: [`KeyAttributeFromExisting`](../../konfiguration/mappings/#keyattributefromexisting), sonst der Primärschlüssel der Spezifikation |

Wird bei `CustomKey`/`Filter` **mehr als ein** Datensatz gefunden, bricht die Verarbeitung mit
einem Fehler ab (`Found more than 1 record`).

---

## Schreiben (Upsert)

Der Konnektor prüft vor jedem Schreibvorgang, ob bereits ein Zieldatensatz existiert
(`GetSingleRequest` bei `PrimaryKey`/`Id`/`CustomKey`, ein gefilterter Liste-Abruf bei
`Filter`). Existiert er, wird nur bei tatsächlicher Werteänderung ein Update ausgelöst —
identische Werte führen zu keiner Anfrage.

- **Anlegen:** `POST` auf den Collection-Pfad
- **Aktualisieren:** `PATCH` (Vorgabe) oder `PUT` (`updateMethod`) auf den Item-Pfad
- Anfrage-Body wird bei Bedarf automatisch in einen aus der Spezifikation abgeleiteten
  Wrapper-Schlüssel eingebettet (z. B. `{"client": {...}}`). Ein Job kann das über
  `SpecialSettings` gezielt abschalten:

  ```json
  { "DoNotWrapRequestBody": true }
  ```

- Ein `409 Conflict` bei Anlegen oder Aktualisieren wird als **transiente** Ausnahme
  behandelt und über die Transitive-Queue erneut verarbeitet (siehe
  [Architektur → Retry- und Fehlerverhalten](../../ueberblick/architektur/#retry--und-fehlerverhalten)).

---

## Löschen

Anders als die SQL-Konnektoren unterstützt der OpenAPI-Konnektor **Löschungen**: Der
Schlüssel wird wie beim Lookup aufgelöst, anschließend folgt ein `DELETE` auf den
Item-Pfad. Bei `TargetKey` vom Typ `Filter` wird der Datensatz zuerst gesucht. Wird keiner
gefunden, schreibt der Konnektor eine Warnung ins Log und sendet kein `DELETE`.

---

## InvokeAction — generische Aktionsaufrufe

Neben dem klassischen CRUD-Zyklus unterstützt die DataBridge seit dieser Version
**Aktions-Jobs**: Ein Job mit `Action` = `InvokeAction` (technisch: jeder Wert, der nicht auf
einen bekannten `JobActionType` passt) ruft einen benannten, nicht-CRUD-Endpunkt auf, statt
einen Datensatz anzulegen oder zu ändern — z. B.
`POST /v2/client_invoices/{id}/send_by_einvoice`.

**Konfiguration:**

| Feld | Bedeutung |
|---|---|
| `Job.Action` | Name der Aktion (z. B. der OpenAPI-`operationId` oder ein sprechender Bezeichner) — beliebig, solange er nicht `NewUpdate`, `Delete`, `TimeStamp` oder `DeleteOnUpdate` entspricht |
| `TableMapping.TargetPath` + `TargetTable` | Ergeben zusammen den Aktionspfad, so wie er in der Spezifikation steht (z. B. `TargetPath = "v2/client_invoices/{id}"`, `TargetTable = "send_by_einvoice"`) |
| `FieldMapping` mit gesetztem `TargetField` | Optional; ergibt den JSON-Request-Body der Aktion |

Ablauf:

1. Der Aktionspfad wird in der Spezifikation nachgeschlagen; die HTTP-Methode wird daraus
   übernommen (bevorzugt `POST`, sonst `PUT`/`PATCH`/`DELETE` — die erste im Pfad
   gefundene, nicht-GET-Methode).
2. Pfadparameter (`{id}` etc.) werden aus `message.Id` (Parametername `id`) bzw. aus
   `message.Attributes` aufgelöst; ein nicht auflösbarer Parameter erzeugt lediglich eine
   Warnung und bleibt im Pfad stehen.
3. Ist mindestens ein Zielfeld gemappt, wird daraus ein JSON-Body gebaut und mitgeschickt.
4. Bei einem ResponseJob wird die Antwort — über den aus der Spezifikation abgeleiteten
   Wrapper-Schlüssel entpackt — als neue Queue-Nachricht mit `ActionCode = NewUpdate`
   weitergereicht.

Ein fehlender Aktionspfad oder eine Antwort ohne 2xx-Statuscode brechen den Job mit einer
Ausnahme ab.

---

## Job-eigene Source-/Target-Pfade (statt nur Mapping-Tabelle)

*(neu, alle Konnektoren betroffen — beim OpenAPI-Konnektor besonders relevant)*

Ein Job kann eigene Felder `Source` und `Target` tragen, die den `SourceTable`/`TargetTable`
der referenzierten Mapping-Zeilen **überschreiben**. Damit lässt sich ein und dasselbe
Mapping für mehrere Ressourcen bzw. Aktionspfade wiederverwenden. Details:
[Jobs → Source und Target](../../konfiguration/jobs/#source-und-target--job-eigene-ressourcenpfade).

---

## Änderungserkennung (Watermark)

| Eigenschaft | Wert |
|---|---|
| Empfohlener WatermarkType | `datetime` |
| WatermarkField | Beliebiger JSON-Pfad im Antwortobjekt (z. B. `updated_at`), der bei jedem gelesenen Datensatz zu einem gültigen Zeitstempel ausgewertet werden kann |

Der Watermark-Wert wird über `job.WatermarkField` als `SelectToken`-Pfad auf jedes gelesene
JSON-Objekt angewendet. Der Filterausdruck (`Job.Filter`) wird — anders als bei D365BC/NAV —
**nicht** in eine standardisierte Query-Syntax übersetzt, sondern unverändert als Query-String
an die Liste-Anfrage angehängt; Aufbau und Platzhalter sind daher von der jeweiligen API
abhängig.

---

## EmbedChildren — eingebettete Kind-Datensätze

*(Quell-Feature ist derzeit nur im D365CE-Connector implementiert, das Zielverhalten dazu
liegt im OpenAPI-Konnektor)*

Verweist ein Feld-Mapping über die SourceOperation `EmbedChildren` auf einen anderen Job,
werden dessen Zeilen bereits beim Lesen in die Nachricht des Elterndatensatzes eingebettet.
Beim Schreiben in eine OpenAPI-Zielressource baut der Konnektor daraus ein **verschachteltes
JSON-Array** (z. B. Rechnungspositionen unter `lines`) und schreibt es als Teil der
Elternanfrage — ohne Feld-für-Feld-Abgleich mit einem eventuell vorhandenen Array im
Zieldatensatz. Details zur Quellseite: [Architektur](../../ueberblick/architektur/) und
[D365CE](../d365ce/).

---

## Caching

OpenAPI-Dokument und aufgelöste Ressourcen-Metadaten (Pfade, Wrapper-Schlüssel, Schema) werden
pro Prozess zwischengespeichert. Ein Neuladen der Spezifikation erfordert einen Neustart der
Function App bzw. ein erneutes Kaltstart-Init.
