Erste Schritte
1. Installation
- ZIP-Datei auf Ihren Windows-Server entpacken (z.B.
C:\SQL2REST\) sql2rest.exedoppelklicken. Windows fragt einmal nach Administrator-Rechten, die der letzte Schritt braucht, um den Dienst einzurichten.- Der Assistent führt Sie durch alles: Verbindung testen, Views erstellen, Dienst installieren
- Danach liegt im Startmenü ein Eintrag SQL2REST. Von dort kommen Sie jederzeit zurück, ohne den Installationsordner zu öffnen. (
StartWizard.batim Installationsordner tut dasselbe.)ab 1.4.5
ODBC Driver 17 wird benötigt, ist aber auf den meisten JTL-Wawi-Servern bereits installiert. Der Assistent prüft die Verbindung automatisch.
Setup
So funktioniert es (interaktiv)
Vom SQL Server zur REST API in 5 Schritten, vollautomatisch.
| Kunden | |
| Aufträge | |
| Produkte | |
| Kategorien | |
| Lagerbestand |
Felder zuordnen
Live-Vorschau
Diesen Schlüssel in Apps & Automationen (n8n, Zapier, Make.com) verwenden, um auf Ihre JTL-Daten zuzugreifen.
sql2_sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4 Als Windows-Dienst installieren. Remote-MCP für Teams einrichten (Team-Tarif).
Auto-Start bei Windows-Boot, läuft im Hintergrund ohne Login.
25 MCP-Tools (u. a. search_products, get_order, list_stock_by_warehouse) für Claude Desktop, Claude Code, Cursor & Windsurf.
Bei externem Zugriff: HTTPS via Reverse Proxy.
2. Erster API-Aufruf
Nach Abschluss des Wizards ist Ihre API sofort einsatzbereit:
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/customers Ersetzen Sie $KEY durch den API-Schlüssel aus dem Setup-Wizard (gespeichert in config.ini).
Das Beispiel zeigt Ihre öffentliche Tunnel-Adresse. Für Tests auf demselben Rechner funktioniert stattdessen http://localhost:8000, im eigenen Netzwerk der Hostname des Servers mit Port 8000. Alle Varianten stehen unter Basis-URL.
Basis-URL: lokal, im Netzwerk, von außen
Die Beispiele auf dieser Seite verwenden https://deinname.jtl2api.com als Platzhalter für den Zugriff von außen. Die Endpunkte und Parameter sind immer identisch, nur die Basis-URL davor ändert sich:
http://localhost:8000 Für Tests direkt auf dem Server, auf dem SQL2REST läuft.
http://192.168.1.50:8000 Hostname oder IP des Servers, erreichbar von anderen Rechnern im LAN.
https://dein-tunnel.jtl2api.com HTTPS über den SQL2REST-Tunnel, ohne Portangabe und ohne offene Ports. Siehe unten.
Die API ist also nicht auf localhost beschränkt: derselbe API-Key und dieselben Endpunkte funktionieren lokal, im Netzwerk und über das Internet.
SQL2REST-Tunnel: HTTPS von außen, ohne Portfreigabe
Der Tunnel ist der Standardweg für den Zugriff von außen. Ein Klick im Dashboard, und SQL2REST bekommt eine feste HTTPS-Adresse. Es muss kein Port in der Firewall geöffnet und keine Domain oder Zertifikat eingerichtet werden, die Datenbank bleibt unsichtbar im eigenen Netz.
- Feste HTTPS-URL, verschlüsselt, weiterhin nur Lesezugriff.
- Keine Portfreigabe, kein Reverse Proxy, keine eigene Domain nötig.
- Der MCP-Endpunkt liegt unter
/mcpderselben Adresse.
Im Team-Tarif enthalten, für Personal als Add-on buchbar. Tarife ansehen.
Statt des Tunnels kannst du den externen Zugang auch selbst aufsetzen, mit einem eigenen Reverse Proxy (Cloudflare Tunnel oder Caddy). SQL2REST selbst läuft in beiden Fällen auf deinem Server.
Claude oder ChatGPT verbinden
Per Anmeldung (Konnektor)
Konnektor hinzufügen, anmelden, fertig. Keine Konfigurationsdatei, kein Schlüssel im Client.
- Läuft im Browser, in der Desktop-App (auch Claude Desktop) und am Handy.
- Keine lokale Sitzung auf dem Rechner nötig.
- Die Freigabeseite zeigt, welche Datengruppen der Client bekommt.
Zwei Voraussetzungen: eine öffentliche Adresse, also Tunnel oder eigener Reverse Proxy, und der Team-Tarif oder höher, weil das Verbinden per Anmeldung über Remote-MCP läuft.
Für ChatGPT gilt genau dieser Weg ebenso: Konnektor hinzufügen und anmelden. Eine Konfigurationsdatei brauchst du dafür nicht.
MCP Server (KI)
SQL2REST kann als MCP-Server für KI-Assistenten genutzt werden. 36 Werkzeuge für Warenwirtschaftsdaten und 2 Resources stehen zur Verfügung.
Am bequemsten ist das Verbinden per Anmeldung. Die Konfigurationsdatei unten brauchst du nur für Clients, die sich nicht im Browser anmelden.
Eingeschaltet wird der MCP-Server im API-Dashboard unter Einstellungen, Abschnitt Remote-MCP, mit dem Schalter Remote-MCP aktivieren. Danach nimmt /mcp Anfragen entgegen. Ein Befehl auf der Kommandozeile ist nicht nötig. Für den Zugriff von außen braucht es den Team-Tarif oder höher; im lokalen Netz ist Remote-MCP in jedem Tarif frei.
Voraussetzung: Die API läuft als Windows-Dienst. Der MCP-Server liest den API-Schlüssel aus der Konfiguration. Für Clients, die ausschließlich über stdio sprechen, gibt es weiterhin sql2rest.exe --mcp auf demselben Rechner.
Für den Zugriff von außen brauchst du HTTPS. Am einfachsten über den SQL2REST-Tunnel (ein Klick, keine Portfreigabe); der MCP-Endpunkt liegt dann unter /mcp der Tunnel-Adresse. Alternativ ein eigener Reverse Proxy mit Cloudflare Tunnel oder Caddy.
Claude Desktop Konfiguration (nicht nötig bei Verbindung per Anmeldung)
{
"mcpServers": {
"SQL2REST": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://dein-tunnel.jtl2api.com/mcp",
"--transport", "http-only",
"--header", "X-API-Key:${SQL2REST_KEY}"
],
"env": { "SQL2REST_KEY": "dein-api-key" }
}
}
} Claude Desktop überträgt native HTTP-MCP-Blöcke nicht zuverlässig, daher läuft die Verbindung über die mcp-remote-Bridge (Node.js 20+ auf der Workstation). http:// für LAN, https:// hinter einem Reverse Proxy. Vollständige Anleitung: JTL-Wawi mit Claude Desktop verbinden.
Verfügbare Tools
search_customers get_customer list_orders get_order get_order_items list_returns get_return get_return_items list_invoices get_invoice_pdf search_products get_product get_product_sales get_stock list_stock_by_warehouse list_warehouses list_shipments list_delivery_notes list_purchase_orders get_purchase_order list_suppliers get_supplier list_goods_receipts get_goods_receipt sync_orders sync_customers list_attributes filter_products_by_attribute list_credit_notes get_credit_note list_employees get_employee get_sales_by_employee list_workflow_logs list_workflow_definitions get_workflow_definition Die Tools zu Lieferanten, Lieferantenbestellungen und Wareneingang stehen bereit, sobald du den Einkauf im Einrichtungs-Assistenten aktiviert hast.
Ein Schlüssel zeigt nur die Werkzeuge, die seine Berechtigungen abdecken. Eine Teilauswahl bleibt die Auswahl von damals: Bereiche, die später dazukommen, sind nicht dabei, bis du sie anhakst. Sind dagegen alle Bereiche angehakt, gilt der Schlüssel als nicht eingeschränkt und bekommt neue Bereiche automatisch mit.
Nach einem Update oder nach neuen Berechtigungen kennt eine bestehende Verbindung die neuen Werkzeuge noch nicht. Über einen Konnektor verbunden: „Tool-Liste aktualisieren" reicht nicht, wenn Bereiche dazugekommen sind, dann in Claude erst Trennen und anschließend Verbinden -- eine Verbindung behält die Bereiche, mit denen sie angelegt wurde. Über die Konfigurationsdatei: den Client vollständig neu starten.
Kompatibel mit Claude (Browser, Desktop und Handy), Claude Code, ChatGPT, Cursor, VS Code mit Copilot, Copilot Studio, Windsurf und jedem anderen MCP-Client.
Fester client_id / client_secret
Die meisten MCP-Clients registrieren sich beim Verbinden selbst (Dynamic Client Registration, RFC 7591) und fragen dich nur nach der Server-URL. Manche Clients können das nicht und erwarten stattdessen eine feste client_id und ein client_secret, die du von Hand einträgst. Für diese gibt es den folgenden Weg.
Relevant zum Beispiel für Cursor, VS Code / Copilot und Copilot Studio im manuellen Modus. Wenn dein Client nach der Verbindung nur eine URL will und dich danach zur Anmeldung schickt, brauchst du diesen Abschnitt nicht.
Adressen des Authorization Servers
SQL2REST betreibt den Authorization Server in der Cloud. Die verbindliche Quelle ist das Metadaten-Dokument; die einzelnen Adressen stehen darin und sind hier nur der Bequemlichkeit halber ausgeschrieben.
Metadata https://sql2rest.com/.well-known/oauth-authorization-server
Issuer https://sql2rest.com
Authorize https://sql2rest.com/oauth/authorize/
Token https://sql2rest.com/api/oauth/token/
Register https://sql2rest.com/api/oauth/register/
JWKS https://sql2rest.com/.well-known/jwks.json Die Adressen enden auf einen Schrägstrich. Das ist kein Schönheitsfehler, sondern nötig: ohne ihn antwortet der Server mit einer Weiterleitung, und nicht jeder Client wiederholt dabei seine Anfrage.
client_id und client_secret anlegen
Ein einziger Aufruf am Register-Endpunkt legt den Client an. Trage bei redirect_uris genau die Rückleitungs-Adresse ein, die dein Client verwendet. Sie wird später zeichengenau geprüft.
curl -X POST https://sql2rest.com/api/oauth/register/ \
-H "Content-Type: application/json" \
-d '{
"client_name": "Cursor auf meinem Notebook",
"redirect_uris": ["http://localhost:3000/callback"],
"token_endpoint_auth_method": "client_secret_post"
}' Die Antwort enthält client_id und client_secret:
{
"client_id": "c_...",
"client_secret": "...",
"client_secret_expires_at": 0,
"token_endpoint_auth_method": "client_secret_post",
"redirect_uris": ["http://localhost:3000/callback"]
} Das client_secret wird genau einmal ausgegeben. Wir speichern davon nur einen Hash und können es nicht erneut anzeigen. Verloren heißt neu anlegen. Behandle es wie ein Passwort und lege es nicht in ein Repository.
Lässt du token_endpoint_auth_method weg, bekommst du einen öffentlichen Client ohne Secret. Das ist der richtige Weg für alles, was PKCE beherrscht, und der Normalfall.
Im Client eintragen
- Authorization- und Token-Adresse aus der Tabelle oben eintragen, dazu client_id und client_secret.
- Als Redirect- beziehungsweise Callback-Adresse exakt den Wert eintragen, den du registriert hast. Schon ein zusätzlicher Schrägstrich führt zur Ablehnung.
- PKCE mit S256 aktivieren. Der Server akzeptiert nichts anderes.
- Als resource die MCP-Adresse deines Systems angeben, also deine öffentliche Adresse mit /mcp am Ende, ohne Schrägstrich dahinter. Damit ist das Token an genau dieses System gebunden.
- Verbindung starten. Du landest auf der SQL2REST-Anmeldung, danach auf der Freigabeseite, und siehst dort, welche Datengruppen freigegeben werden.
Freigegeben wird immer nur die Schnittmenge aus dem, was dein Administrator dir zugewiesen hat, und dem, was der Client anfragt. Ein Client kann sich damit nicht mehr Rechte holen, als du hast.
Zugang
Authentifizierung
Jede API-Anfrage erfordert einen API-Schlüssel als HTTP-Header:
X-API-Key: your-api-key-here Der Header-Name ist standardmäßig X-API-Key und kann in config.ini unter [API] HeaderName geändert werden.
Hinweis: Query-Parameter-Authentifizierung wird nicht unterstützt. Der API-Schlüssel muss als Header gesendet werden.
Endpunkt-Referenz
Alle Daten-Endpunkte erfordern einen gültigen API-Schlüssel und eine aktive Lizenz. Feldnamen hängen von Ihrer Spaltenzuordnung im Setup-Wizard ab.
Die Beispiele auf dieser Seite zeigen die kanonischen Katalogfelder. Ein Feld, das der Einrichtungs-Assistent auf Ihrer Installation nicht zuordnen konnte, fehlt in der tatsächlichen Antwort, statt als leerer Wert dazustehen.
Alle Endpunkte unterstützen: sort, order, limit, offset, mandant. Details siehe unten.
Interne JTL-IDs (JTL_-Präfix)
Ab v1.1.80 gibt jede Entität interne JTL-IDs zurück, die Sie für Rückschreib-Workflows mit der offiziellen JTL-REST-API benötigen.
Welche ID an welcher Entität hängt
JTL_CustomerId (Kunden), JTL_SalesOrderId + JTL_CustomerId (Aufträge), JTL_ItemId + JTL_SalesOrderId + JTL_ProductId (Auftragspositionen), JTL_ProductId (Artikel), JTL_DeliveryNoteId + JTL_SalesOrderId + JTL_CustomerId (Lieferscheine), JTL_ShipmentId + JTL_DeliveryNoteId + JTL_SalesOrderId (Lieferungen).
Jeder Endpunkt hier lässt sich auch im Chat fragen, in Claude wie in ChatGPT. Der Reiter Im Chat zeigt bei jedem, wie die Frage lautet. Verbinden
Kunden
/customers Kunden auflisten mit optionaler Suche, Sortierung und Paginierung.
| Parameter | Typ | Beschreibung |
|---|---|---|
| search | string | LIKE-Suche über Name und Nummer |
| customer_number | string | Exakter Filter nach Kundennummer |
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/customers?search=Müller" {
"data": [
{
"CustomerNumber": "10001",
"CompanyName": "Müller GmbH",
"FirstName": "Thomas",
"LastName": "Müller",
"CustomerEmail": "[email protected]",
"Address": {
"City": "Frankfurt",
"Country": "DE"
}
}
],
"total": 3,
"limit": 100,
"offset": 0
} Du fragst„Suche den Kunden Müller GmbH."
Der Assistent ruft dafür search_customers auf.
Kunden abrufen
/customers/{number} Einzelnen Kunden nach Kundennummer abrufen.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/customers/10001 {
"data": {
"CustomerNumber": "10001",
"CompanyName": "Müller GmbH",
"FirstName": "Thomas",
"LastName": "Müller",
"CustomerEmail": "[email protected]",
"CustomerPhone": "069 1234567",
"Address": {
"Street": "Musterstraße 5",
"PostalCode": "60311",
"City": "Frankfurt",
"Country": "DE"
}
}
} Du fragst„Wie lautet die Adresse von Kunde 10001?"
Der Assistent ruft dafür get_customer auf.
Aufträge
/orders Aufträge auflisten mit Filtern nach Kunde, Status und Zeitraum.
| Parameter | Typ | Beschreibung |
|---|---|---|
| customer | string | Filter nach Kundennummer |
| customer_number | string | Exakter Filter nach Kundennummer |
| order_number | string | Exakter Filter nach Auftragsnummer |
| external_order_id | string | Exakter Filter nach externer Bestell-ID |
| status | string | Filter nach Status |
| storno | int | Storno-Flag: 0 = aktive Aufträge, 1 = stornierte (wie JTL nStorno) |
| type | string | Auftragstyp, z. B. B für normale Aufträge (wie JTL cType). storno=0 und type=B entspricht JTLs "verkauft pro Tag". |
| from | date | Startdatum (YYYY-MM-DD) |
| to | date | Enddatum (YYYY-MM-DD) |
Welche Felder deine Antwort trägt, hängt von deiner Einrichtung ab. Der Assistent legt beim Erkennen fest, welche Spalten ausgeliefert werden. Das Beispiel zeigt deshalb eine typische Antwort, nicht die einzig mögliche. Felder, die bei dir fehlen, sind kein Fehler.
Felder mit dem Präfix CF_ sind deine eigenen Felder aus der Wawi. Wie viele es sind und wie sie heißen, ist bei jeder Anlage anders.
S2R_CanaryRevOrders22 trägt die Revisionsnummer deiner Datenbanksichten. Für die Auswertung deiner Aufträge kannst du es ignorieren.
Ein fehlender Wert kommt in zwei Formen zurück, und beide bedeuten dasselbe: null und leerer Text. In den Adressen steht etwa CompanyName als leerer Text da, wo keine Firma hinterlegt ist, Phone dagegen als null.
Die Liste liefert die Datensätze in data, dazu total, limit und offset für den Seitenlauf. Der Einzelabruf liefert nur data mit einem einzelnen Datensatz, ohne diese drei Angaben. Der Datensatz selbst sieht in beiden Fällen gleich aus.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/orders?from=2024-01-01&status=Versendet" {
"data": [
{
"OrderNumber": "40217",
"CustomerNumber": "10842",
"OrderDate": "2026-03-11T09:14:02.483000",
"TotalAmount": 47.95,
"NetAmount": 40.29,
"Currency": "EUR",
"ExternalOrderId": "999-1234567-1234567",
"PaymentStatus": "Paid",
"Storno": 0,
"OrderType": "B",
"Platform": "Amazon.de",
"OrderColor": null,
"OrderStatus": null,
"OrderHoldReason": null,
"ShippingCost": null,
"CreatedByEmployeeId": 1,
"CreatedByEmployeeName": "Administrator",
"SalesEmployeeId": 1,
"SalesEmployeeName": "Administrator",
"AccountManagerId": null,
"AccountManagerName": null,
"ExpectedDeliveryDate": null,
"OrderAnnotation": "FBA Amazon Order 999-1234567-1234567",
"OrderHint": null,
"S2R_CanaryRevOrders22": 22,
"CF_Lieferhinweis": null,
"BillingAddress": {
"Street": "Lindenstrasse 42",
"PostalCode": "60329",
"City": "Frankfurt",
"Country": "Deutschland",
"CompanyName": "Muster Handels GmbH",
"FirstName": "Erika",
"LastName": "Musterfrau",
"Email": "[email protected]",
"Phone": "01701234567"
},
"ShippingAddress": {
"Street": "Lindenstrasse 42",
"PostalCode": "60329",
"City": "Frankfurt",
"Country": "Deutschland",
"CompanyName": "",
"FirstName": "Erika",
"LastName": "Musterfrau",
"Email": "[email protected]",
"Phone": null
}
}
],
"total": 1247,
"limit": 1,
"offset": 0
} Du fragst„Welche Aufträge sind seit Januar noch offen?"
Der Assistent ruft dafür list_orders auf.
Auftrag abrufen
/orders/{number} Einzelnen Auftrag nach Auftragsnummer abrufen.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/orders/86957 Du fragst„Was steht im Auftrag AU-86957?"
Der Assistent ruft dafür get_order auf.
Auftragspositionen
/orders/{number}/items Positionen eines Auftrags abrufen.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/orders/86957/items Du fragst„Welche Artikel stecken in Auftrag AU-86957?"
Der Assistent ruft dafür get_order_items auf.
Retouren
/returns Drei Endpunkte für Retouren (RMA): Kopfdaten auflisten, eine einzelne Retoure abrufen und die Positionen einer Retoure abrufen.
Retouren auflisten mit Filtern nach Kunde, Status, RMA-Nummer und Zeitraum. Standardmäßig nach Retourendatum absteigend sortiert (neueste zuerst).
| Parameter | Typ | Beschreibung |
|---|---|---|
| customer | string | Filter nach Kundennummer |
| customer_number | string | Exakter Filter nach Kundennummer |
| status | string | Filter nach Status |
| rma_number | string | Exakter Filter nach RMA-Nummer |
| search | string | LIKE-Suche über die RMA-Nummer |
| from | date | Startdatum (YYYY-MM-DD) |
| to | date | Enddatum (YYYY-MM-DD) |
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/returns?from=2024-01-01&status=Offen" {
"data": [
{
"RmaNumber": "Ret-118",
"ReturnDate": "2026-04-02T11:27:55.120000",
"Status": "Eingetroffen",
"CustomerNumber": "10842",
"ReferenceOrderNumber": "40217",
"Value": 8.99,
"ValueGross": 8.99,
"ValueNet": 7.554622,
"GoodsValueGross": 8.99,
"GoodsValueNet": 7.554622,
"ShippingRefund": 0.0,
"Warehouse": "Zentrallager",
"CommentExternal": "",
"CommentInternal": ""
}
],
"total": 37,
"limit": 1,
"offset": 0
} Du fragst„Welche Retouren sind aktuell offen?"
Der Assistent ruft dafür list_returns auf.
Retoure abrufen
/returns/{rma_number} Einzelne Retoure nach RMA-Nummer abrufen.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/returns/Ret-118" Du fragst„Was steht in Retoure Ret-118?"
Der Assistent ruft dafür get_return auf.
Retourenpositionen
/returns/{rma_number}/items Positionen einer Retoure abrufen.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/returns/Ret-118/items" Du fragst„Welche Artikel wurden bei Ret-118 zurückgeschickt?"
Der Assistent ruft dafür get_return_items auf.
Felder der /returns-Antwort
Kopfdaten je Retoure. Feldnamen können je nach Spaltenzuordnung im Setup-Wizard abweichen.
| Feld | Typ | Beschreibung |
|---|---|---|
| RmaNumber | string | RMA-Nummer der Retoure |
| ReturnDate | date | Retourendatum |
| Status | string | Status der Retoure |
| CustomerNumber | string | Kundennummer |
| ReferenceOrderNumber | string | Zugehörige JTL-Auftragsnummer |
| Value | number | Gesamtwert der Retoure |
| ValueGross | number | Gesamtwert brutto (inkl. Steuer) |
| ValueNet | number | Gesamtwert netto (ohne Steuer) |
| GoodsValueGross | number | Warenwert brutto |
| GoodsValueNet | number | Warenwert netto |
| ShippingRefund | number | Erstattete Versandkosten |
| Warehouse | string | Lager, in das zurückgebucht wird |
| CommentExternal | string | Kommentar für den Kunden, oft leerer Text |
| CommentInternal | string | Interner Kommentar, oft leerer Text |
Felder der /returns/{rma_number}/items-Antwort
Eine Zeile je zurückgesendeter Position.
| Feld | Typ | Beschreibung |
|---|---|---|
| Sku | string | Artikelnummer (SKU) |
| ArticleName | string | Artikelname |
| Quantity | number | Zurückgesendete Menge |
| ReturnReason | string | Retourengrund |
| ArticleCondition | string | Zustand des Artikels |
| PriceNet | number | Positionspreis netto |
| PriceGross | number | Positionspreis brutto |
| DeliveryNoteNumber | string | Zugehörige Lieferscheinnummer |
| ItemStatus | string | Status der Position |
Rechnungen
/invoices Rechnungen auflisten mit Filtern nach Kunde, Status und Zeitraum.
| Parameter | Typ | Beschreibung |
|---|---|---|
| customer | string | Filter nach Kundennummer |
| status | string | Filter nach Status |
| from / to | date | Datumsbereich (YYYY-MM-DD) |
| order_number | string | Filter nach Auftragsnummer |
| min_dunning_levelab 1.4.7 | integer | Nur Rechnungen ab dieser Mahnstufe. 0 liefert die noch nicht angemahnten. |
Mit dem Parameter ?order= können alle Rechnungen zu einem bestimmten Auftrag abgerufen werden:
Mahnstufen
Gefiltert wird ab einer Stufe, nicht auf eine genaue. Die Stufen gehören zu einer Mahngruppe, und die Gruppen sind unterschiedlich tief: ein exakter Vergleich ließe ausgerechnet die am härtesten angemahnten Kunden weg. Die Antwort führt DunningLevel und DunningLevelName. Bau die Bezeichnung nie aus der Zahl: die Zahl ist der Bezeichnung einen Schritt voraus, Stufe 3 heißt „2. Mahnung". Nicht angemahnt ist null, nie 0. OpenAmount wird bei Rechnungskorrekturen negativ.
Die Mahnfelder sind auf den meisten Anlagen leer, weil dort gar nicht gemahnt wird. null heißt also nicht, dass eine Rechnung offen ist.
OpenAmount kann negativ werden, etwa bei einer Rechnungskorrektur. Wenn du Summen über offene Beträge bildest, filtere vorher auf Werte größer als null, sonst rechnest du dir die Summe klein.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/invoices?min_dunning_level=3" {
"data": [
{
"InvoiceNumber": "88214",
"InvoiceDate": "2026-03-11T09:15:00.221000",
"InvoiceOrderNumber": "40217",
"DunningLevel": null,
"DunningLevelName": null,
"IsDunned": false,
"DunningDate": null,
"DunningFee": 0.0,
"OpenAmount": 0.0
}
],
"total": 1193,
"limit": 1,
"offset": 0
} Du fragst„Welche Rechnungen stehen mindestens auf der 2. Mahnung?"
Der Assistent ruft dafür list_invoices auf.
Rechnungs-PDFs
/invoices/{number}/pdf Rechnungs-PDF nach Rechnungsnummer herunterladen. Standardmäßig deaktiviert; kann im Setup-Wizard oder in config.ini aktiviert werden.
Die Suche extrahiert den numerischen Teil der Rechnungsnummer (RE-12345 → 12345) und sucht rekursiv in allen konfigurierten Ordnern und Unterordnern. Bei mehreren Treffern wird die neueste Datei verwendet.
Gibt die PDF-Datei direkt als Download zurück (Content-Type: application/pdf). Bei nicht gefundener Rechnung: 404.
Konfiguration
Es reicht, den Hauptordner anzugeben - Unterordner wie Jahres- oder Monatsverzeichnisse werden automatisch durchsucht:
InvoicePdfPath = \\SERVER\Buchhaltung\Rechnungsausgang
; Findet auch: \2026\04\RE-12345.pdf curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/invoices/86774/pdf --output rechnung.pdf Du fragst„Schick mir die Rechnung RE-2026-3391 als PDF."
Der Assistent ruft dafür get_invoice_pdf auf.
Gutschriften ab 1.4.7
/credit-notes Gutschriften abrufen wie Rechnungen: Nummer, Datum, Betrag, Status, Kunde, zugehörige Rechnung und Mitarbeiter.
| Parameter | Typ | Beschreibung |
|---|---|---|
| customer_number | string | Filter nach Kundennummer |
| invoice_number | string | Filter nach der Rechnungsnummer, zu der die Gutschrift gehört |
| employee_id | integer | Filter nach der Mitarbeiter-ID, die die Gutschrift ausgestellt hat |
| search | string | Suche nach der Gutschriftsnummer |
| from / to | date | Datumsbereich (YYYY-MM-DD) |
| mandant | integer | Mandant (Datenbank), Standard 1 |
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/credit-notes?customer_number=10842&from=2026-03-01" {
"data": [
{
"CreditNoteId": 412,
"CreditNoteNumber": "118",
"CreditNoteDate": "2026-03-14T16:02:41.755000",
"Amount": 8.99,
"Status": null,
"Cancelled": 0,
"ExternalOrderId": null,
"EmployeeId": 1,
"CustomerNumber": "10842",
"InvoiceNumber": "88214",
"EmployeeName": "Administrator",
"OrderNumber": "40217",
"OrderCount": 1
}
],
"total": 7
} Du fragst„Zeig mir alle Gutschriften von Kunde 10042 seit Juli."
Der Assistent ruft dafür list_credit_notes auf.
Gutschrift abrufen
/credit-notes/{nummer} Felder je Gutschrift
| Feld | Typ | Beschreibung |
|---|---|---|
| CreditNoteId | integer | Interne Kennung der Gutschrift |
| CreditNoteNumber | string | Gutschriftsnummer |
| CreditNoteDate | string | Erstellungsdatum |
| Amount | number | Betrag der Gutschrift |
| Status | string | Status aus der Wawi |
| Cancelled | integer | Storniert (1) oder nicht (0) |
| ExternalOrderId | string | Externe Bestell-ID, oft null |
| EmployeeId | integer | Kennung des Mitarbeiters, der die Gutschrift ausgestellt hat |
| CustomerNumber | string | Kundennummer |
| InvoiceNumber | string | Nummer der Rechnung, zu der die Gutschrift gehört |
| EmployeeName | string | Name dieses Mitarbeiters |
| OrderNumber | string | Zugehörige Auftragsnummer, falls ermittelbar |
| OrderCount | integer | Anzahl der verknüpften Aufträge |
Die Zuordnung zum Auftrag läuft über die Rechnung. Hängen mehrere Aufträge an einer Rechnung, nennt OrderNumber den ersten davon und OrderCount sagt, wie viele es sind, damit eine 1 zu n nicht als 1 zu 1 gelesen wird.
Du bekommst den Gesamtbetrag je Gutschrift, nicht die einzelnen Artikelzeilen. Welche Artikel in welcher Menge gutgeschrieben wurden, kommt in einer späteren Version.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/credit-notes/118 Du fragst„Wie hoch ist Gutschrift 118?"
Der Assistent ruft dafür get_credit_note auf.
Artikel
/products Artikel auflisten mit optionaler Suche.
| Parameter | Typ | Beschreibung |
|---|---|---|
| search | string | LIKE-Suche über Name und Nummer |
| sku | string | Exakter Filter nach Artikelnummer (SKU) |
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/products?search=Fahrradhelm" {
"data": [
{
"SKU": "ART-001",
"ProductName": "Fahrradhelm",
"ProductPrice": 39.90,
"ProductPriceGross": 47.48,
"ProductVAT": 19,
"GTIN": "4006381333931"
}
],
"total": 842,
"limit": 100,
"offset": 0
} Du fragst„Finde den Artikel Fahrradhelm."
Der Assistent ruft dafür search_products auf.
Artikel abrufen
/products/{sku} Einzelnen Artikel nach Artikelnummer (SKU) abrufen.
Felder der /products-Antwort
Die folgende Tabelle listet alle Felder, die der /products-Endpunkt zurückgeben kann.
| Feld | Typ | Beschreibung |
|---|---|---|
| SKU | string | Artikelnummer (SKU) |
| ProductName | string | Artikelname |
| ProductDescription | string | Lange Artikelbeschreibung |
| ProductPrice | number | Nettopreis (ohne Steuer) |
| ProductPriceGross | number | Bruttopreis (inkl. Steuer) |
| ProductVAT | number | Steuersatz in Prozent |
| GTIN | string | EAN/GTIN-Barcode |
| ManufacturerName | string | Herstellername |
| Category | string | Primäre Kategorie des Artikels |
| ProductGroup | string | Warengruppe des Artikels |
| TaricCode* | string | Zolltarifnummer (TARIC) |
| CountryOfOrigin* | string | Herkunftsland |
| JTL_ProductId | number | Advanced, opt-in: interner JTL-Artikelschlüssel |
| CF_… | mixed | Eigene Felder, opt-in im Setup-Wizard aktiviert |
| attributes | object | Merkmale; auf /products via ?include=attributes, auf /products/{sku} immer |
* TaricCode und CountryOfOrigin sind optionale Felder und müssen im Setup-Wizard (Feldauswahl) aktiviert werden, damit sie von der API und dem MCP zurückgegeben werden. Standardmäßig sind sie deaktiviert.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/products/ART-001 {
"data": {
"SKU": "ART-001",
"ProductName": "Fahrradhelm",
"ProductDescription": "Leichter Fahrradhelm mit Ventilationsschlitzen.",
"ProductPrice": 39.90,
"ProductPriceGross": 47.48,
"ProductVAT": 19,
"GTIN": "4006381333931",
"ManufacturerName": "Herstellername GmbH",
"Category": "Fahrradzubehör"
}
} Du fragst„Was kostet Artikel ART-001?"
Der Assistent ruft dafür get_product auf.
Merkmale auflisten
/attributes SQL2REST erkennt JTL-Merkmale (standardisierte Varianten-Beschreibungen wie Farbe, Größe, Material) und stellt sie über drei Wege zur Verfügung: einen Katalog-Endpunkt, eingebettete Werte auf Artikeln und einen Filter-Parameter.
Alle Merkmal-Definitionen mit ihren möglichen Werten, gruppiert nach Name.
| Parameter | Typ | Beschreibung |
|---|---|---|
| mandant | integer | Mandanten-ID (Standard: 1) |
JTL-IDs für Write-Back
Jede Position führt bereits die internen JTL-IDs mit: id auf dem Attribut (kMerkmal) und id auf jedem Wert (kMerkmalWert). Nützlich für Write-Back über die offizielle JTL-API.
Attribute ausblenden
Über den Abschnitt [MERKMALE] in der config.ini können einzelne Attribute aus allen Antworten ausgeblendet werden (Komma-getrennt, Groß-/Kleinschreibung wird ignoriert). Versuche, diese Namen als Filter zu nutzen, liefern HTTP 400.
[MERKMALE]
Disabled = InternalCode, LegacyFlag Sprache
Attribut-Übersetzungen werden bei der View-Erstellung fixiert (Standard: Deutsch, kSprache=1). Eine laufzeitseitige Umschaltung über den Accept-Language-Header ist aktuell nicht aktiv. Bei Bedarf an Mehrsprachigkeit bitte Kontakt aufnehmen.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/attributes {
"data": [
{
"id": 5,
"name": "Farbe",
"values": [
{ "id": 12, "value": "Rot" },
{ "id": 13, "value": "Blau" },
{ "id": 14, "value": "Grün" }
]
},
{
"id": 6,
"name": "Größe",
"values": [
{ "id": 21, "value": "S" },
{ "id": 22, "value": "M" },
{ "id": 23, "value": "L" }
]
}
],
"total": 2
} Du fragst„Welche Merkmale gibt es für Artikel?"
Der Assistent ruft dafür list_attributes auf.
Merkmalswerte auf einem Artikel
/products/{sku} Der Endpunkt /products/{sku} liefert immer ein attributes-Objekt, gruppiert nach Attributname. Bei /products opt-in über ?include=attributes, um N+1 zu vermeiden.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/products/ART-002 {
"data": {
"SKU": "ART-002",
"ProductName": "T-Shirt",
"attributes": {
"Farbe": ["Rot"],
"Größe": ["M"]
}
}
} Du fragst„Welche Farbe hat Artikel ART-002?"
Der Assistent ruft dafür get_product auf.
Artikel nach Attribut filtern
/products Der Parameter ?attribute[name]=wert filtert Artikel. Attributnamen sind die deutschen JTL-Originalnamen (kleingeschrieben). Mehrere Filter werden mit UND verknüpft.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/products?attribute[farbe]=rot&attribute[groesse]=m&include=attributes" {
"data": [
{
"SKU": "ART-002",
"ProductName": "T-Shirt",
"attributes": { "Farbe": ["Rot"], "Größe": ["M"] }
}
],
"total": 4
} Du fragst„Welche Artikel gibt es in Rot und Größe M?"
Der Assistent ruft dafür filter_products_by_attribute auf.
Bestand
/stock Lagerbestand auflisten mit optionalem SKU-Filter. Jede Zeile enthält: SKU, AvailableStock, ReservedStock, IncomingStock, BlockedStock und StockLevel. JTL_ProductId ist advanced/opt-in.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/stock?sku=ART-001" {
"data": [
{
"SKU": "ART-001",
"JTL_ProductId": 4821,
"AvailableStock": 37.0,
"ReservedStock": 5.0,
"IncomingStock": 0.0,
"BlockedStock": 0.0,
"StockLevel": 42.0
}
]
} Du fragst„Wie viel haben wir von ART-001 noch auf Lager?"
Der Assistent ruft dafür get_stock auf.
Bestand je Lager
/products/{sku}/stock Lagerbestand eines Artikels aufgeteilt nach Lager abrufen. Eine Zeile pro Lager mit WarehouseId, WarehouseName, WarehouseActive und StockInWarehouse.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/products/ART-001/stock {
"data": [
{
"SKU": "ART-001",
"WarehouseId": 1,
"WarehouseName": "Hauptlager Hamburg",
"WarehouseActive": 1,
"StockInWarehouse": 42.0
}
]
} Du fragst„Wie verteilt sich der Bestand von ART-001 auf die Lager?"
Der Assistent ruft dafür list_stock_by_warehouse auf.
Bestand aller Artikel je Lager
/stock/by-warehouse Lagerbestand aller Artikel aufgeteilt nach Lager (Sammelabruf für den Bestandsabgleich). Eine Zeile je Artikel und Lager mit SKU, WarehouseId, WarehouseName, WarehouseActive, StockInWarehouse und AvailableInWarehouse. Seitenweise über limit und offset.
| Parameter | Typ | Beschreibung |
|---|---|---|
| sku | string | Filter nach Artikelnummer |
| warehouse_id | int | Filter nach JTL-Lager-ID (kWarenLager) |
| active_only | bool | Nur aktive JTL-Artikel zurückgeben (inaktive oder gelöschte mit Restbestand überspringen) |
| shop_id | int | Nur Artikel, die diesem JTL-Shop zugewiesen sind (kShop). Benötigt aktualisierte Ansichten: einmalig in der JTL-Wawi die SQL2REST-Ansichten neu erstellen. Ohne shop_id wird der vollständige Katalog zurückgegeben (abwärtskompatibel). |
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/stock/by-warehouse?shop_id=1" Du fragst„Gib mir den Bestand aller Artikel je Lager."
Der Assistent ruft dafür list_stock_by_warehouse auf.
Lieferungen
/shipments Lieferungen auflisten mit Filtern nach Auftrag, Versanddienstleister und Zeitraum.
| Parameter | Typ | Beschreibung |
|---|---|---|
| order | string | Filter nach Auftragsnummer |
| carrier | string | Filter nach Versanddienstleister |
| from / to | date | Datumsbereich (YYYY-MM-DD) |
| sort_order | string | Sortierrichtung: asc oder desc (statt order) |
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/shipments?order=86957" {
"data": [
{
"OrderNumber": "86957",
"CarrierName": "DHL",
"TrackingID": "00340434161094123456",
"ShippingDate": "2026-04-15"
}
]
} Du fragst„Wurde Auftrag 86957 schon versendet?"
Der Assistent ruft dafür list_shipments auf.
Lieferscheine
/delivery-notes Lieferscheine auflisten mit optionaler Suche und Datumsfilter.
| Parameter | Typ | Beschreibung |
|---|---|---|
| search | string | LIKE-Suche über Name und Nummer |
| from / to | date | Datumsbereich (YYYY-MM-DD) |
| include | string | items: Positionen einschließen |
Einzelnen Lieferschein nach Lieferscheinnummer abrufen, Positionen werden automatisch eingeschlossen.
Lieferscheine mit Positionen in einem Aufruf abrufen.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/delivery-notes?include=items" {
"data": [
{
"DeliveryNoteNumber": "LS-10042",
"DeliveryNoteDate": "2024-03-15",
"OrderNumber": "86957",
"CustomerNumber": "10001",
"items": [
{
"ArticleNumber": "ART-001",
"ArticleName": "Produkt A",
"Quantity": 2
}
]
}
],
"total": 12,
"limit": 100,
"offset": 0
} Du fragst„Zeig mir die letzten Lieferscheine."
Der Assistent ruft dafür list_delivery_notes auf.
Einkauf (Wareneingang)
/goods-receipts Drei Endpunkte für Einkaufsprozesse: Wareneingänge (stock movements), Lieferanten (suppliers) und Lieferantenbestellungen (purchase orders). Standardmäßig deaktiviert, im Setup-Wizard unter Schritt 3 "Einkauf / Wareneingang" aktivieren. Wenn aus, geben die Endpunkte 404 zurück.
Wareneingänge auflisten (Buchungen auf Lagerplätzen). Lieferant und Bestellung sind nur für Buchungsart 10 (Wareneingang aus Bestellung) befüllt; andere Buchungsarten liefern NULL für diese Spalten.
| Parameter | Typ | Beschreibung |
|---|---|---|
| booking_type | integer | JTL Buchungsart (10 = Wareneingang, 170 = Retoure, 40 = FBA, etc.) |
| booking_type_not_in | string | Komma-Liste auszuschließender Buchungsarten (z.B. 40,170) |
| supplier_id | integer | Filter nach Lieferanten-ID (kLieferant) |
| sku | string | Filter nach Artikel-SKU |
| has_po | boolean | true = nur Zeilen mit verknüpfter Bestellung |
| since / until | date | Datumsbereich auf received_at (YYYY-MM-DD) |
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/goods-receipts?booking_type=10&since=2026-01-01" {
"data": [
{
"receipt_id": 12482,
"received_at": "2026-04-15",
"booked_at": "2026-04-15T09:21:00",
"delivered_at": "2026-04-14",
"booking_type": 10,
"booking_type_label": "Wareneingang",
"qty": 250,
"unit_cost": 4.85,
"sku": "RAW-001",
"article_name": "Rohstoff A",
"supplier_id": 12,
"supplier_name": "Musterlieferant GmbH",
"supplier_no": "LIEF-0012",
"po_id": 1019,
"po_number": "1019",
"po_qty_ordered": 250,
"po_qty_delivered_total": 250,
"po_qty_still_open": 0
}
],
"total": 22,
"limit": 100,
"offset": 0
} Du fragst„Welche Wareneingänge kamen seit Januar rein?"
Der Assistent ruft dafür list_goods_receipts auf.
Wareneingang abrufen
/goods-receipts/{receipt_id} Einzelnen Wareneingang nach JTL-ID (kWarenLagerEingang) abrufen.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/goods-receipts/12482 Du fragst„Was steht hinter Wareneingang 12482?"
Der Assistent ruft dafür get_goods_receipt auf.
Lieferanten
/suppliers Lieferanten auflisten mit Suche, Aktiv-Filter und Adress- und Konditions-Spalten.
| Parameter | Typ | Beschreibung |
|---|---|---|
| search | string | LIKE-Suche über Firmenname, Lieferantennummer, Ansprechpartner |
| aktiv | string | Filter nach Aktiv-Flag (Y/N) |
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/suppliers?aktiv=Y" {
"data": [
{
"supplier_id": 12,
"supplier_no": "LIEF-0012",
"company_name": "Musterlieferant GmbH",
"contact": "Max Mustermann",
"street": "Musterstraße 1",
"postal_code": "10115",
"city": "Berlin",
"country": "DE",
"email": "[email protected]",
"vat_id": "DE123456789",
"payment_term_days": 30,
"discount_days": 10,
"discount_pct": 2.0,
"currency": "EUR",
"active": "Y"
}
],
"total": 17,
"limit": 100,
"offset": 0
} Du fragst„Welche Lieferanten sind aktuell aktiv?"
Der Assistent ruft dafür list_suppliers auf.
Lieferant abrufen
/suppliers/{supplier_id} Einzelnen Lieferanten nach JTL-ID (kLieferant) abrufen.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/suppliers/12 Du fragst„Wie erreiche ich Lieferant 12?"
Der Assistent ruft dafür get_supplier auf.
Lieferantenbestellungen
/purchase-orders Lieferantenbestellungen als flache Header+Position-Zeilen. Eine Zeile pro Bestellposition. Für Soll-Ist-Reporting open_only=true verwenden.
| Parameter | Typ | Beschreibung |
|---|---|---|
| supplier_id | integer | Filter nach Lieferanten-ID |
| status | integer | Filter nach Bestell-Status (nStatus) |
| open_only | boolean | true = nur Positionen mit qty_open > 0 |
| since / until | date | Datumsbereich auf created_at (YYYY-MM-DD) |
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/purchase-orders?open_only=true" Du fragst„Welche Bestellungen sind noch offen?"
Der Assistent ruft dafür list_purchase_orders auf.
Bestellung abrufen
/purchase-orders/{po_id} Einzelne Bestellung mit verschachtelten Positionen unter positions abrufen.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/purchase-orders/1019 {
"data": {
"po_id": 1019,
"po_number": "1019",
"created_at": "2026-03-12",
"expected_delivery": "2026-04-15",
"status": 2,
"currency": "EUR",
"supplier_id": 12,
"supplier_name": "Musterlieferant GmbH",
"supplier_no": "LIEF-0012",
"positions": [
{
"position_id": 4421,
"sku": "RAW-001",
"article_name": "Rohstoff A",
"qty_ordered": 250,
"qty_delivered": 250,
"qty_open": 0,
"unit_price_net": 4.85
}
]
}
} Du fragst„Was steht in Bestellung 1019?"
Der Assistent ruft dafür get_purchase_order auf.
Auswertungen (/reports)
Verkaufszahlen je Artikel ab 1.4.6
/reports/product-sales Beantwortet "wie oft wurde dieser Artikel verkauft" in einer Abfrage. Die Summierung passiert in der Datenbank, nicht durch das Durchblättern aller Aufträge. Mehrere Artikelnummern lassen sich mit Komma trennen.
| Parameter | Typ | Beschreibung |
|---|---|---|
| sku | string | Artikelnummer, mehrere mit Komma getrennt. Pflicht. |
| date_from | date | Beginn des Zeitraums (JJJJ-MM-TT). Ohne Angabe der laufende Monat. |
| date_to | date | Ende des Zeitraums (JJJJ-MM-TT). Ohne Angabe heute. |
| date_basis | string | order (Standard) zählt die bestellte Menge, delivery die ausgelieferte. |
| mandant | integer | Mandant (Datenbank), Standard 1 |
Zwei Zahlen, nicht eine
Bestellt (order) zählt die Positionen nicht stornierter Verkaufsaufträge im Zeitraum, also dieselbe Abgrenzung wie "verkauft pro Tag" in der Wawi. Noch nicht versendete Mengen sind enthalten, und der Netto-Umsatz kommt mit. Ausgeliefert (delivery) zählt die Lieferscheinpositionen, also was das Lager verlassen hat. Dort gibt es keinen Umsatz, weil Lieferscheinpositionen keinen Preis führen; ein aus dem Auftrag geborgter Wert würde am falschen Ereignis hängen. Jede Antwort nennt die verwendete Basis mit, damit die beiden Zahlen nicht verwechselt werden.
Felder je Artikel
| Feld | Typ | Beschreibung |
|---|---|---|
| sku | string | Artikelnummer |
| quantity_sold / quantity_delivered | number | Menge im Zeitraum: quantity_sold auf der Basis order, quantity_delivered auf der Basis delivery |
| order_count / delivery_note_count | number | Anzahl der Aufträge (Basis order) beziehungsweise Lieferscheine (Basis delivery), in denen der Artikel vorkam |
| net_revenue | number | Netto-Umsatz. Nur auf der Basis order, sonst null. |
| date_basis | string | Welche der beiden Zahlen die Antwort liefert (Top-Level der Antwort, nicht je Artikel) |
Die Artikelzeilen stehen unter results, nicht unter data. Die Antwort führt zusätzlich sku, from_date, to_date, date_basis und mandant auf oberster Ebene.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/reports/product-sales?sku=ART-001&date_from=2026-08-01" {
"sku": ["ART-001"],
"from_date": "2026-08-01",
"to_date": "2026-09-13",
"date_basis": "order",
"results": [
{
"sku": "ART-001",
"quantity_sold": 34,
"order_count": 29,
"net_revenue": 1356.60
}
],
"mandant": 1
} Du fragst„Wie oft wurde ART-001 im August verkauft?"
Der Assistent ruft dafür get_product_sales auf.
Umsatz je Mitarbeiter ab 1.4.7
/reports/sales-by-employee Beantwortet „was hat jeder verkauft" in einer Abfrage. Die Summierung passiert in der Datenbank, nicht durch das Durchblättern tausender Aufträge.
| Parameter | Typ | Beschreibung |
|---|---|---|
| date_from | date | Beginn des Zeitraums (JJJJ-MM-TT). Ohne Angabe der laufende Monat. |
| date_to | date | Ende des Zeitraums (JJJJ-MM-TT). Ohne Angabe heute. |
| attribution | string | Wem der Auftrag zugerechnet wird: created_by (Standard), sales_employee oder account_manager. |
| employee_id | integer | Auf eine einzelne Mitarbeiter-ID einschränken. Namen dazu über /employees. |
| type | string | Filter nach Auftragsart, etwa B für normale Verkaufsaufträge. |
| platform | string | Filter nach Verkaufskanal |
| country | string | Filter nach Rechnungsland |
| storno | integer | Filter nach Storno-Kennzeichen (0 aktiv, 1 storniert) |
| include_gross_profit | boolean | Rohertrag mitrechnen. Standard false, weil dafür die Positionen dazugelesen werden. |
| mandant | integer | Mandant (Datenbank), Standard 1 |
Wem der Umsatz zugerechnet wird
Derselbe Auftrag zählt je nach Wahl auf eine andere Person, deshalb ist das eine Frage und kein Detail. created_by ist der Ersteller, je Auftrag festgeschrieben und damit die einzige Angabe, deren Bedeutung unstrittig ist: daher der Standard. sales_employee ist der Mitarbeiter am Auftrag, auftragsseitig, aber in der Wawi je Auftrag umsetzbar. account_manager ist der Betreuer am Kundenstamm und damit eine Momentaufnahme: ordnest du einen Kunden neu zu, ändert sich die Zahl auch für seine alten Aufträge. Jede Antwort nennt die verwendete Zurechnung mit.
Felder je Mitarbeiter
| Feld | Typ | Beschreibung |
|---|---|---|
| employee_id | integer | Mitarbeiter-ID |
| employee_name | string | Name des Mitarbeiters |
| orders | number | Anzahl der Aufträge, Stornos nicht mitgezählt |
| customers | number | Anzahl verschiedener Kunden |
| net_order_value | number | Netto-Umsatz |
| gross_order_value | number | Brutto-Umsatz |
| average_order_value | number | Durchschnittlicher Auftragswert |
| cancelled_orders / cancelled_net_value | number | Anzahl der Stornos und ihr Netto-Wert, getrennt ausgewiesen |
Stornos werden getrennt ausgewiesen statt weggelassen. Umsatz und Stornos zusammen sind alle Aufträge, die durch die Filter kamen, damit nichts unangekündigt aus der Summe fällt.
Der Rohertrag rechnet mit dem heutigen Einkaufspreis aus dem Artikelstamm, nicht mit dem zum Verkaufszeitpunkt. Bei alten Aufträgen oder geänderten Einkaufspreisen weicht der Wert deshalb ab. Die Antwort schreibt das als cost_basis dazu.
Die Mitarbeiterzeilen stehen unter results, nicht unter data.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/reports/sales-by-employee?date_from=2026-08-01" {
"from_date": "2026-08-01",
"to_date": "2026-09-13",
"attribution": "created_by",
"results": [
{
"employee_id": 7,
"employee_name": "M. Keller",
"orders": 128,
"customers": 94,
"net_order_value": 48213.40,
"gross_order_value": 57373.95,
"average_order_value": 376.66,
"cancelled_orders": 3,
"cancelled_net_value": 421.00
}
],
"mandant": 1
} Du fragst„Was hat jeder Mitarbeiter im August verkauft?"
Der Assistent ruft dafür get_sales_by_employee auf.
Mitarbeiter ab 1.4.7
/employees Das Mitarbeiterverzeichnis aus der Wawi: Name, Abteilung und ob die Person noch aktiv ist. Damit lassen sich die Mitarbeiter-IDs auflösen, die an Aufträgen, Rechnungen und Gutschriften hängen.
Im Chat heißen dieselben Werkzeuge list_employees und get_employee.
| Parameter | Typ | Beschreibung |
|---|---|---|
| active_only | boolean | Nur aktive Mitarbeiter. Standard true; mit false kommen die Ausgeschiedenen mit. |
| mandant | integer | Mandant (Datenbank), Standard 1 |
Einzelnen Mitarbeiter nach ID abrufen: GET /employees/{id}.
Felder je Mitarbeiter
| Feld | Typ | Beschreibung |
|---|---|---|
| EmployeeId | integer | Mitarbeiter-ID, die an Aufträgen und Belegen steht |
| EmployeeName | string | Name |
| Login | string | Anmeldename in der Wawi |
| Department | string | Abteilung |
| string | E-Mail-Adresse | |
| Active | integer | Noch aktiv (1) oder ausgeschieden (0) |
Ausgeschiedene Mitarbeiter bleiben auffindbar. Das ist der Zweck des Verzeichnisses: eine Auswertung über das letzte Jahr bleibt richtig, auch wenn jemand das Unternehmen inzwischen verlassen hat.
Das Verzeichnis kommt vollständig, ohne limit und offset. Eine Belegschaft ist keine Datenmenge, durch die man blättert.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/employees {
"data": [
{
"EmployeeId": 7,
"EmployeeName": "M. Keller",
"Login": "mkeller",
"Department": "Vertrieb",
"Email": "[email protected]",
"Active": 1
}
]
} Du fragst„Welche Mitarbeiter sind aktuell aktiv?"
Der Assistent ruft dafür list_employees auf.
Workflow-Protokoll ab 1.4.7
/workflow-logs Die Protokolleinträge deiner JTL-Workflows: wann welcher Workflow gelaufen ist, was er gemeldet hat und wo er abgebrochen ist. Filterbar nach Schweregrad, Workflow, Objekt und Zeitraum, dazu eine Volltextsuche über die Meldung.
Eigene Berechtigung. Die Workflow-Protokolle liegen seit 1.4.7 in der Berechtigung Workflows und hängen an keinem anderen Bereich. Im Chat heißt das Werkzeug list_workflow_logs.
| Parameter | Typ | Beschreibung |
|---|---|---|
| severity | string | Schweregrad, mehrere per Komma: 1=Fehler 2=Warnung 4=Info 5=Aktion |
| workflow | integer | Genau ein Workflow, über dieselbe WorkflowId wie in den Definitionen |
| search | string | Volltextsuche über Meldung, ObjectId und Namen des Workflows |
| from / to / limit / offset | date / integer | Zeitraum (YYYY-MM-DD), Seitenlauf mit 100 Einträgen je Seite |
Alle Felder, die Schweregrade und die Beispiele stehen auf der eigenen Seite Workflow-Log.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/workflow-logs?severity=1&from=2026-09-01" Du fragst„Welche Workflows sind gestern auf einen Fehler gelaufen?"
Der Assistent ruft dafür list_workflow_logs auf.
Workflow-Definitionen ab 1.4.8
/workflow-definitions Das Workflow-Protokoll sagt, was passiert ist. Die Workflow-Definitionen sagen, warum: bei welchem Ereignis ein Workflow startet, welche Bedingungen er prüft und welche Aktionen er ausführt. Rein lesend wie alles hier: In die Wawi wird nichts geschrieben, und es wird kein Workflow ausgelöst.
WorkflowId ist derselbe Wert wie im Workflow-Protokoll. Darüber werden aus zwei Listen eine Antwort: Der Protokolleintrag nennt den Abbruch, der Workflow dahinter nennt den Grund. Keine Übersetzungstabelle, kein Suchen über den Namen.
Keine neue Berechtigung nötig. Die Workflow-Definitionen liegen in derselben Berechtigung wie die Workflow-Protokolle, denn es ist dieselbe Sache aus zwei Blickwinkeln. Wer die Protokolle schon freigegeben hat, bekommt sie mit dem Update, ohne etwas anzuhaken.
Im Chat heißen die Werkzeuge list_workflow_definitions und get_workflow_definition. Das zweite liefert Kopf, Bedingungen und Aktionen eines Workflows in einer einzigen Antwort.
| Parameter | Typ | Beschreibung |
|---|---|---|
| search | string | Suche über den Namen des Workflows |
| workflow | integer | Genau ein Workflow, über dieselbe WorkflowId wie im Protokoll |
| event | integer | Filter nach dem Ereignis-Code |
| object | integer | Filter nach dem Objekt-Code |
| sort / order | string | Sortierung nach Spaltenname, Richtung asc oder desc. Standard ist der Name des Workflows, aufsteigend |
| limit / offset / page | integer | Seitenlauf, Standard 100 Workflows je Seite |
| mandant | integer | Mandant (Datenbank), Standard 1 |
ObjectType und EventType stehen immer, EventLabel nicht: Die Bezeichnung kommt aus dem Ereigniskatalog der Wawi, und der ist dort oft nur teilweise gefüllt. Filtere deshalb über die Codes, und baue keine Anzeige, die ohne die Bezeichnung bricht.
Ob ein Workflow aktiv ist, steht nicht in den Daten: Dieses Feld führt die Wawi nicht. Ob er läuft, zeigt das Workflow-Protokoll.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/workflow-definitions?search=Auslieferung" {
"data": [
{
"WorkflowId": 42,
"WorkflowName": "Auslieferung vorbereiten",
"ObjectType": 6,
"EventType": 1,
"EventLabel": null,
"Position": 0,
"WorkflowType": 0,
"Application": 63,
"ConditionLink": 0
}
],
"total": 79,
"limit": 100,
"offset": 0
} Du fragst„Welche Workflows laufen bei uns auf Aufträge?"
Der Assistent ruft dafür list_workflow_definitions auf.
Bedingungen eines Workflows ab 1.4.8
/workflow-definitions/{id}/conditions Die Bedingungen eines Workflows in ihrer Reihenfolge: die geprüfte Eigenschaft, der Operator im Klartext und der Wert, gegen den verglichen wird.
Ein Beispiel. Im Protokoll steht, ein Auslieferungs-Workflow sei an der Bedingung Auftragspositionen.KomplettLieferbarAusLager abgebrochen. Dieselbe Bedingung findest du hier wieder, aber unter dem Namen GruppePositionen.KomplettLieferbarAusLager. Warum sie anders heißt, steht gleich unter der Tabelle.
So ist eine Bedingung aufgebaut
Eine Bedingung besteht aus drei Angaben. Am Beispiel von oben:
| Die geprüfte Eigenschaft | → | GruppePositionen.KomplettLieferbarAusLager.contains.BrowsePK |
| Der Operator | → | ist gleich |
| Der Wert | → | 1 (Zentrallager) |
Eine Regel kann mehrere Bedingungen haben. Jede Bedingung hat genau einen Pfad. Ein Pfad besteht aus Teilen. Die Teile bekommst du einzeln in PropertyParts, jeweils mit der Angabe, woher sie stammen. Zusammengesetzt mit Punkten ergeben sie den PropertyPath.
Felder je Bedingung
| Feld | Typ | Beschreibung |
|---|---|---|
| ConditionId | integer | Kennung der Bedingung |
| Position | integer | Reihenfolge innerhalb des Workflows |
| PropertyPath | string | Die geprüfte Eigenschaft als lesbarer Pfad |
| PropertyParts | array | Dieselben Teile einzeln, je mit Herkunft |
| OperatorCode | integer | Der Vergleichsoperator als Zahl |
| OperatorLabel | string | Derselbe Operator im Klartext, etwa enthält |
| OperatorMember | string | Derselbe Operator als technischer Bezeichner |
| OperatorTakesOperand | boolean | Ob dieser Operator überhaupt einen Vergleichswert hat |
| OperatorProvenance | string | Worauf die Bezeichnung steht |
| ComparisonValue | string | Der Wert, gegen den verglichen wird |
| ComparisonValueLabel | string | null | Derselbe Wert als Name, wenn er auf einen Datensatz verweist |
Jede Angabe trägt ihre Herkunft mit. OperatorLabel ist die deutsche Formulierung, die du aus der Wawi kennst, und OperatorProvenance sagt, worauf sie steht. Einen Operator, den wir noch nicht benennen können, bekommst du mit seiner Zahl und ohne Bezeichnung: Die Bedingung verschwindet nie, sie bleibt nur unbenannt.
Die Wawi zeigt andere Namen an, als die API liefert, weil die API den gespeicherten Bezeichner zurückgibt und die Wawi daraus einen Anzeigenamen macht. Gemeint ist dasselbe:
| In der Wawi steht | Die API liefert | |
|---|---|---|
| Auftragspositionen | → | GruppePositionen |
| enthält | → | contains |
| Anmerkung | → | cAnmerkung |
| Enthält_Standard-Lager | → | EnthaeltStandardLager |
Zurückrechnen kannst du das nicht. Umlaute und Sonderzeichen fallen beim Speichern weg, und aus EnthaeltStandardLager lässt sich Enthält_Standard-Lager nicht wiederherstellen. Vergleiche den Pfad deshalb nicht Zeichen für Zeichen mit dem, was am Bildschirm steht.
Wie viele Teile ein Pfad hat, ist nicht festgelegt. Baue also nichts, was eine bestimmte Anzahl voraussetzt.
Auch der Aufbau kann sich unterscheiden, obwohl dieselbe Frage gemeint ist. Mal steht die Antwort im Pfad selbst, etwa …EnthaeltStandardLager verglichen gegen True. Mal steht sie im Vergleichswert:
BrowsePK am Ende des Pfades heißt: Hier wurde ein Datensatz über einen Auswahldialog gewählt. Der Vergleichswert ist dann dessen Nummer. Zeigt er auf ein Lager, steht der Name daneben in ComparisonValueLabel. Eines siehst du hier allerdings nicht: ob jemand die Auswahl in der Wawi offen gelassen hat. Eine unfertige Regel sieht in den Daten aus wie eine fertige.
Der Vergleichswert ist immer Text. Und zwar so, wie die Wawi ihn gespeichert hat, nicht so, wie er eingetippt wurde. Aus einer eingetippten 3 wird 3,0000, mit Komma. Wenn du damit rechnest, ersetze das Komma vorher durch einen Punkt. Manche Operatoren haben gar keinen Wert, etwa ist leer. Ob einer zu erwarten ist, sagt dir OperatorTakesOperand.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/workflow-definitions/42/conditions {
"data": [
{
"ConditionId": 311,
"WorkflowId": 42,
"Position": 0,
"PropertyPath": "GruppePositionen.KomplettLieferbarAusLager.contains.BrowsePK",
"PropertyParts": [
{ "PropertyIndex": 1, "PropertyName": "GruppePositionen", "PropertyParentType": "jtlCore.Models.PropertyProxy.Auftrag.Auftrag" },
{ "PropertyIndex": 2, "PropertyName": "KomplettLieferbarAusLager", "PropertyParentType": "jtlCore.Models.PropertyProxy.Auftrag.AuftragPositionen" },
{ "PropertyIndex": 3, "PropertyName": "contains", "PropertyParentType": "jtlDatabase.classes.jtlDBClasses.jtlWarenlagerList" },
{ "PropertyIndex": 4, "PropertyName": "BrowsePK", "PropertyParentType": "jtlDatabase.classes.jtlDBClasses.jtlWarenlagerList" }
],
"OperatorCode": 1,
"OperatorLabel": "ist gleich",
"OperatorMember": "Equal",
"OperatorTakesOperand": true,
"OperatorProvenance": "BELEGT",
"ComparisonValue": "1",
"ComparisonValueLabel": "Zentrallager"
},
{
"ConditionId": 312,
"WorkflowId": 42,
"Position": 1,
"PropertyPath": "GruppePositionen.KomplettLieferbarAusLager.EnthaeltStandardLager",
"PropertyParts": [
{ "PropertyIndex": 1, "PropertyName": "GruppePositionen", "PropertyParentType": "jtlCore.Models.PropertyProxy.Auftrag.Auftrag" },
{ "PropertyIndex": 2, "PropertyName": "KomplettLieferbarAusLager", "PropertyParentType": "jtlCore.Models.PropertyProxy.Auftrag.AuftragPositionen" },
{ "PropertyIndex": 3, "PropertyName": "EnthaeltStandardLager", "PropertyParentType": "jtlDatabase.classes.jtlDBClasses.jtlWarenlagerList" }
],
"OperatorCode": 1,
"OperatorLabel": "ist gleich",
"OperatorMember": "Equal",
"OperatorTakesOperand": true,
"OperatorProvenance": "BELEGT",
"ComparisonValue": "True",
"ComparisonValueLabel": null
},
{
"ConditionId": 313,
"WorkflowId": 42,
"Position": 2,
"PropertyPath": "GruppePositionen.BruttopreisGesamt",
"PropertyParts": [
{ "PropertyIndex": 1, "PropertyName": "GruppePositionen", "PropertyParentType": "jtlCore.Models.PropertyProxy.Auftrag.Auftrag" },
{ "PropertyIndex": 2, "PropertyName": "BruttopreisGesamt", "PropertyParentType": "jtlCore.Models.PropertyProxy.Auftrag.AuftragPositionen" }
],
"OperatorCode": 3,
"OperatorLabel": "ist größer als",
"OperatorMember": "GreaterThan",
"OperatorTakesOperand": true,
"OperatorProvenance": "BELEGT",
"ComparisonValue": "3,0000",
"ComparisonValueLabel": null
},
{
"ConditionId": 314,
"WorkflowId": 42,
"Position": 3,
"PropertyPath": "GruppeZahlungen.Zahlungen.contains.Auftrag.cAnmerkung",
"PropertyParts": [
{ "PropertyIndex": 1, "PropertyName": "GruppeZahlungen", "PropertyParentType": "jtlCore.Models.PropertyProxy.Auftrag.Auftrag" },
{ "PropertyIndex": 2, "PropertyName": "Zahlungen", "PropertyParentType": "jtlCore.Models.PropertyProxy.Auftrag.AuftragZahlung" },
{ "PropertyIndex": 3, "PropertyName": "contains", "PropertyParentType": "jtlDatabase.classes.jtlDBClasses.jtlZahlungList" },
{ "PropertyIndex": 4, "PropertyName": "Auftrag", "PropertyParentType": "jtlCore.Models.PropertyProxy.Zahlung.Zahlung" },
{ "PropertyIndex": 5, "PropertyName": "cAnmerkung", "PropertyParentType": "jtlCore.Models.PropertyProxy.Auftrag.AuftragBestellung" }
],
"OperatorCode": 9,
"OperatorLabel": "enthält",
"OperatorMember": "Contains",
"OperatorTakesOperand": true,
"OperatorProvenance": "BELEGT",
"ComparisonValue": "Express",
"ComparisonValueLabel": null
}
],
"total": 4,
"workflow_id": 42
} Du fragst„Welche Bedingungen prüft unser Auslieferungs-Workflow?"
Der Assistent ruft dafür get_workflow_definition auf.
Aktionen eines Workflows ab 1.4.8
/workflow-definitions/{id}/actions Was ein Workflow tut, sobald seine Bedingungen zutreffen: die Aktionen in ihrer Reihenfolge, jede mit ihrer Art und ihrem Parametersatz.
Felder je Aktion
| Feld | Typ | Beschreibung |
|---|---|---|
| ActionId | integer | Kennung der Aktion |
| Position | integer | Reihenfolge innerhalb des Workflows |
| ActionType | string | Die Art der Aktion, wie die Wawi sie führt |
| Parameters | object | Die Parameter der Aktion; leer bei unbekannter Art |
Die Aktionsparameter sind eine feste Auswahl. Was nicht dazugehört, wird nicht ausgeliefert, und eine Aktionsart, die wir noch nicht auflösen, erscheint mit ihrer Bezeichnung und mit leerem Parameters. Ein leerer Parametersatz heißt also: diese Art lösen wir noch nicht auf. Er heißt nie, dass die Aktion nichts tut.
Was dieser Endpunkt nicht auflöst
Einen Namen bekommst du nur für Warenlager. Bei jedem anderen Verweis bekommst du die Nummer. Worauf sie zeigt, sagt dir der PropertyParentType des letzten Pfadteils: eine Zahlungsart, ein Shop, eine Kundengruppe. Sag also „Zahlungsart Nr. 9" statt „ist gleich 9".
Ein Vergleichswert kann mehrere Nummern enthalten, getrennt durch Semikolon, etwa 16;17. Das ist eine Mehrfachauswahl und keine große Zahl. Für solche Werte kommt nie ein Name.
Zwei Dinge sind ausdrücklich keine Lücke. Alle Operatoren, die die Wawi kennt, sind benannt. Und die Aktionsparameter sind eine feste Auswahl: Eine Aktionsart, die wir noch nicht auflösen, behält ihre Bezeichnung und kommt ohne Parameter. Ein leerer Parametersatz heißt „diese Art lösen wir noch nicht auf", nie „diese Aktion tut nichts".
Die Form der Antwort steht fest. Was heute nicht aufgelöst wird, füllt später ein Feld, das jetzt null ist. Es fällt nichts weg und nichts wird umbenannt. Du kannst also darauf bauen.
curl -H "X-API-Key: $KEY" https://deinname.jtl2api.com/workflow-definitions/42/actions {
"data": [
{
"ActionId": 187,
"WorkflowId": 42,
"Position": 0,
"ActionType": "a:jtlAktionEmail",
"Parameters": {
"Subject": "Ihre Sendung ist unterwegs",
"Empfaenger": "{{ Vorgang.Kunde.EMail }}",
"CancelOnError": "true"
}
},
{
"ActionId": 188,
"WorkflowId": 42,
"Position": 1,
"ActionType": "a:jtlAktionUnbekannt",
"Parameters": {}
}
],
"total": 2,
"workflow_id": 42
} Du fragst„Was macht dieser Workflow, wenn er zutrifft?"
Der Assistent ruft dafür get_workflow_definition auf.
Cross-API: SQL2REST lesen + JTL schreiben
/orders Die Felder mit JTL_-Präfix sind Schlüsselwerte aus Ihrer JTL-Datenbank. Sie sind im Wizard standardmäßig deaktiviert (siehe Abschnitt "Erweitert" im Setup) und werden nur benötigt, wenn Sie zusätzlich zur read-only API von SQL2REST auch über die offizielle JTL-API in JTL schreiben (zum Beispiel Auftragsstatus aktualisieren). Reine Leseintegrationen können diese Felder ignorieren.
# 1. Read order via SQL2REST (this API)
GET /orders?order_number=12345
# response includes: JTL_SalesOrderId: 9, JTL_CustomerId: 42
# 2. Use JTL_SalesOrderId to update via the official JTL API
PATCH https://your-jtl-server/api/eazybusiness/v1/salesOrder/9 Du fragst„Wie lautet die JTL_SalesOrderId für Auftrag 12345?"
Der Assistent ruft dafür get_order auf.
Sync-Endpunkte
/sync/orders Vorverbundene Endpunkte für CRM-Integrationen. Liefern Daten aus mehreren Tabellen in einem Aufruf.
Aufträge mit Kunden-, Rechnungs- und Versanddaten in einem Aufruf.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/sync/orders?since=2026-08-01" Du fragst„Synchronisiere alle Aufträge seit dem 1. August."
Der Assistent ruft dafür sync_orders auf.
Kunden synchronisieren
/sync/customers Batch-Kundensynchronisation mit höherem Limit (bis zu 1000).
Firma, Name, Kontakt und Adresse stammen aus der Standardadresse des Kunden.
curl -H "X-API-Key: $KEY" "https://deinname.jtl2api.com/sync/customers?limit=1000" {
"data": [
{
"CustomerNumber": "10001",
"CompanyName": "Müller GmbH",
"FirstName": "Thomas",
"LastName": "Müller",
"CustomerEmail": "[email protected]",
"CustomerPhone": "069 1234567",
"CustomerCreatedDate": "2024-01-15",
"Address": {
"Street": "Musterstraße 5",
"PostalCode": "60311",
"City": "Frankfurt",
"Country": "DE"
}
}
],
"total": 842,
"limit": 1000,
"offset": 0
} Du fragst„Zieh mir alle Kunden für den CRM-Abgleich."
Der Assistent ruft dafür sync_customers auf.
Abfrage-Grundlagen
Filtern & Suchen
Zwei Arten von Filtern stehen zur Verfügung:
Textsuche (LIKE)
Der search-Parameter durchsucht Name und Nummer. Verfügbar auf /customers und /products.
Exakte Filter
Filterparameter liefern exakte Übereinstimmungen:
| Parameter | Verfügbar auf |
|---|---|
| customer | /orders, /invoices, /returns, /sync/orders |
| customer_number | /customers, /orders, /returns |
| order_number | /orders |
| external_order_id | /orders |
| status | /orders, /invoices, /returns |
| rma_number | /returns |
| sku | /stock, /products |
| order | /shipments |
| carrier | /shipments |
Sortierung & Paginierung
Die meisten Listen-Endpunkte unterstützen Sortierung und Paginierung.
| Parameter | Standard | Beschreibung |
|---|---|---|
| sort | — | Sortierfeld (beliebiger Spaltenname der View) |
| order | asc | Sortierrichtung: asc oder desc |
| limit | 100 | Ergebnisse pro Seite (1–500) |
| offset | 0 | Ergebnisse überspringen |
Bei /invoices und /delivery-notes filtert ?order_number= nach der Auftragsnummer, und das ist die empfohlene Schreibweise. Aus Rücksicht auf bestehende Integrationen gilt weiterhin: ein Wert auf ?order=, der nicht asc oder desc ist, wird als Auftragsnummer gelesen. Ein Tippfehler in der Richtung liefert deshalb eine leere Seite statt einer Fehlermeldung.
Nicht alle Endpunkte sortieren und blättern. Ohne sort, order, limit und offset arbeiten: /employees, /attributes, /warehouses, /shops, /mandanten sowie die beiden Auswertungen unter /reports. Sie liefern ihre Menge vollständig. mandant versteht dagegen jeder Endpunkt außer /mandanten.
Hinweis: /shipments verwendet sort_order statt order.
Datumsfilter
Zeitraum-Filter über from/to-Parameter (jeweils einschließlich).
Multi-Mandant
Wenn Sie mehrere JTL-Datenbanken konfiguriert haben, verwenden Sie den mandant-Parameter, um die Zieldatenbank auszuwählen. Standard ist 1.
Mandanten-Limits nach Tarif
| Tarif | Mandanten |
|---|---|
| Trial | 1 |
| Personal | 1 |
| Agency | 5 |
| Enterprise | Unbegrenzt |
Vertiefung
Auto-Discovery
Der Setup-Wizard analysiert automatisch Ihr JTL-Datenbankschema und erkennt die richtigen Spalten für jeden Endpunkt.
- Erkennt Ihre JTL-Wawi Version automatisch (1.5 bis 2.0)
- Ordnet Standardfelder automatisch zu (Kundennummer, Bestelldatum, SKU etc.)
- Nicht erkannte Felder können manuell per Dropdown zugeordnet werden
- Spaltenvorschau zeigt echte Beispieldaten aus Ihrer Datenbank
Eigene Felder
SQL2REST erkennt automatisch benutzerdefinierte Felder (Eigene Felder) in Ihrer JTL-Datenbank und macht sie per API verfügbar.
- Unterstützt Artikel-, Kunden- und Bestellungsfelder
- Alle JTL-Feldtypen: Text, Ganzzahl, Dezimalzahl, Datum
- Pro Gruppe aktivierbar/deaktivierbar im Setup-Wizard (Schritt 3)
- Standardmäßig deaktiviert - nur aktive Felder erscheinen in der API
Eigene Felder erscheinen in der API-Antwort mit dem Präfix CF_:
{
"SKU": "ART-001",
"ProductName": "Widget",
"CF_Energie_KJ": 500,
"CF_Zutaten": "Mehl, Zucker, Butter",
"CF_WEEE_Pickup": 1
} Um Eigene Felder nachträglich zu aktivieren, führen Sie den Setup-Wizard erneut aus.
Community Mapping
Wenn Sie eine Spalte manuell zuordnen, können Sie diese Zuordnung anonym teilen, um die Erkennung für alle Nutzer zu verbessern.
- Nur bei expliziter Zustimmung im Wizard (Opt-in)
- Es werden nur Tabellen-/Spaltennamen geteilt, niemals Geschäftsdaten
Details zum Datenschutz: /datenschutz
Add-ons
Diese Add-ons erweitern SQL2REST um eigene Bereiche mit eigener Dokumentation:
Dazu kommt ein Add-on anderer Art: es erweitert nicht den Funktionsumfang, sondern die Erreichbarkeit.
SQL2REST-Tunnel
Eine verschlüsselte Adresse (HTTPS) für API und MCP, ohne offene Ports und ohne eigene Domain. Ohne sie geht das Verbinden per Anmeldung nicht, weil ein Konnektor deine Wawi von außen erreichen muss.
In Team, Agency und Enterprise enthalten. Im Personal-Tarif für 19 €/Monat zubuchbar, im laufenden Monat anteilig; nur bei monatlicher Zahlung, bei Jahresabo über den Support.
Einrichtung: im Dashboard dazubuchen (inklusive in Team oder höher), dann in den Einstellungen unter Fernzugriff Von überall wählen und die Adresse bestätigen. Einzelheiten unter Basis-URL und Tunnel.
Betrieb
Aktualisierung
Meldet das API-Dashboard eine neue Version, genügt ein Klick auf den Hinweis: SQL2REST lädt das Update, prüft es per SHA-256 und spielt es ein. update.bat im Installationsordner tut dasselbe von Hand und ist die Notlösung, wenn der Knopf nicht durchläuft.
Update-Benachrichtigung
Wenn eine neue Version verfügbar ist, zeigt das API-Dashboard automatisch einen Hinweis mit dem Knopf, der das Update startet. Sie öffnen es über den Startmenü-Eintrag SQL2REST.
Von Hand aktualisieren (Notlösung)
- Rechtsklick auf
update.bat→ „Als Administrator ausführen" - Versionsvergleich prüfen und mit Y bestätigen
Manuelles Update (offline)
- Laden Sie die neueste Version aus Ihrem Web-Dashboard herunter
- Führen Sie als Administrator aus:
update.bat C:\Pfad\zur\sql2rest-latest.zip
Was passiert
- Prüft auf neue Versionen (Auto-Update) oder nutzt die angegebene ZIP-Datei
- Dienst wird über NSSM gestoppt (Fallback: taskkill)
config.iniwird gesichert und nach dem Update wiederhergestellt- Download wird per SHA-256-Hash verifiziert
- Dienst wird automatisch neu gestartet
Was sich je Version geändert hat, steht im Changelog.
Setup erneut öffnen
Um den Setup-Wizard erneut auszuführen (z.B. nach einem JTL-Update, damit neue Felder wie ProductGroup in die Views übernommen werden):
- Im Startmenü den Eintrag SQL2REST öffnen. Das öffnet das API-Dashboard im Browser, ohne den Dienst zu stoppen. (Ohne Startmenü-Eintrag:
ApiDashboard.batim Installationsordner.) - Auf der Karte "Lokale REST-API" auf das Zahnrad-Symbol klicken. Das startet den Setup-Wizard erneut.
- Den Schema-Schritt erneut durchlaufen. Danach werden neue und aktualisierte Felder in die Views übernommen.
Der Setup-Wizard legt die SQL-Views neu an. Bestehende Konfiguration (API-Schlüssel, Port, Eigene Felder) bleibt erhalten.
Deinstallieren
Deinstallieren entfernt SQL2REST von diesem Rechner: den Nur-Lese-Benutzer und die Ansichten aus der Datenbank, die lokale Konfiguration sowie den Windows-Dienst. Ihre JTL-Wawi Daten bleiben unberührt.
- Im API-Dashboard unter Einstellungen, Eintrag Deinstallieren
- Erfordert die SQL-Server-Admin-Zugangsdaten aus der Einrichtung, nicht den SQL2REST-Benutzer (der ist Nur-Lese)
- Das lässt sich nicht rückgängig machen. Um SQL2REST danach wieder zu nutzen, richten Sie es neu ein:
sql2rest.exeerneut ausführen.
Fehlercodes
| Status | Bedeutung |
|---|---|
| 200 | Erfolg |
| 402 | Lizenz erforderlich oder Tarif-Limit überschritten |
| 403 | Ungültiger API-Schlüssel |
| 404 | Ressource nicht gefunden / Mandant nicht konfiguriert |
| 500 | Datenbankfehler |
| 503 | Setup erforderlich (Wizard unter /setup ausführen) |
402-Antwortformat
{
"detail": {
"message": "Valid license required",
"url": "https://sql2rest.com/jtl-wawi#pricing"
}
} Testversion
Die 14-Tage-Testversion wird automatisch beim Setup erstellt und hat folgende Einschränkungen:
| Merkmal | Trial | Bezahlt |
|---|---|---|
| Endpunkte | /customers, /orders, /products, /sync | Alle |
| Mandanten | 1 | Je nach Tarif |
| Laufzeit | 14 Tage | Unbegrenzt |
Nicht verfügbare Endpunkte liefern Status 402 mit Upgrade-Link. Upgraden Sie unter /jtl-wawi#pricing.