Verbinde Lavesy mit deiner Handwerkersoftware, mit n8n, Zapier oder Make: Über die REST-API liest und schreibst du Artikel, Bestände, Buchungen, Projekte und Bestellungen; Webhooks melden dir Ereignisse wie „Mindestbestand unterschritten" in Echtzeit.
Erste Schritte
- Die API ist im Entrepreneur-Tarif enthalten.
- Als Admin unter Einstellungen → Schnittstellen einen API-Schlüssel anlegen, Bereiche (z. B. Artikel lesen, Bestand schreiben) wählen und den Schlüssel sofort kopieren — er wird nur einmal angezeigt.
- Jede Anfrage geht an
https://lavesy.de/api/v1und trägt den Schlüssel im Header:
# Schlüssel aus Einstellungen → Schnittstellen (beginnt mit lvs_)
export LAVESY_API_KEY="…"
curl -H "Authorization: Bearer $LAVESY_API_KEY" \
"https://lavesy.de/api/v1/inventory/articles?limit=50&offset=0"Authentifizierung
Sende den Schlüssel als Authorization: Bearer lvs_…. Schlüssel haben Bereiche mit Lese- oder Schreibrecht: articles, stock, movements, projects, customers, suppliers, purchase_orders, goods_receipts, outbound. Schreiben schließt Lesen ein. Lesende Anfragen (GET) brauchen bereich:read, alle anderen bereich:write.
Ein Schlüssel handelt im Namen deines Unternehmens und nie über seine Bereiche hinaus. Er kann ein Ablaufdatum haben und lässt sich jederzeit widerrufen. Schreibende Zugriffe landen im Audit-Log. Halte Schlüssel geheim — nie in Browser-Code oder öffentliche Repositories.
Pagination
Listen sind seitenweise: ?limit= (Anzahl) und ?offset= (Startposition). Die Antwort enthält die Einträge und die Gesamtzahl:
{
"items": [ { "id": "…", "sku": "KAB-NYM-3x1,5", "name": "NYM-J 3x1,5 mm²" } ],
"count": 1240
}Weiterblättern, solange offset + limit < count.
Fehler
Fehler kommen immer als JSON {"detail": "…", "code": "…"}. code ist stabil und maschinenlesbar, detail ein lesbarer Text.
HTTP/1.1 403 Forbidden
{
"detail": "Der API-Schlüssel hat keinen Zugriff auf diesen Bereich.",
"code": "api_key_scope"
}401 api_key_invalid— Schlüssel unbekannt, widerrufen oder abgelaufen.403 api_key_scope— der Schlüssel hat keinen Zugriff auf diesen Bereich.403 insufficient_permissions— die Aktion ist nicht erlaubt.403 tier_required— dein Tarif enthält die API nicht (mehr).409— Konflikt, z. B. Nummer schon vergeben oder Status passt nicht.422 validation_error— ungültige Eingabe; Details untererrors.429 rate_limited— zu viele Anfragen, siehe unten.
Rate-Limits
Pro Schlüssel gelten 1.200 Anfragen pro Stunde und 120 pro Minute. Darüber antwortet die API mit 429 und code: "rate_limited"; der Header Retry-After nennt die Wartezeit in Sekunden. Warte so lange und wiederhole dann die Anfrage.
Webhooks
Unter Einstellungen → Schnittstellen → Webhooks trägst du eine https-Adresse ein und wählst Ereignisse. Lavesy schickt dann bei jedem Ereignis ein POST mit JSON. Mit „Test senden" probierst du die Verbindung aus, im Zustellprotokoll siehst du Status und Antwortcode jeder Zustellung.
Ereignisse
stock.low— Der Bestand eines Artikels ist unter seinen Mindestbestand gefallen (einmal pro Unterschreitung).movement.created— Eine Lagerbuchung wurde erstellt (Einbuchen, Entnahme, Umlagerung, Korrektur …).goods_receipt.posted— Ein Wareneingang wurde gebucht.purchase_order.created— Eine Bestellung wurde angelegt.purchase_order.status_changed— Der Status einer Bestellung hat sich geändert (z. B. bestellt, geliefert, storniert).article.created— Ein Artikel wurde angelegt.article.updated— Ein Artikel wurde geändert.project.material_booked— Material wurde auf ein Projekt gebucht (Warenausgang mit Projektbezug).
Aufbau
POST /dein-endpunkt HTTP/1.1
Content-Type: application/json
User-Agent: LAVESY-Webhooks/1.0
Lavesy-Event: stock.low
Lavesy-Delivery: 0193f1d2-…
Lavesy-Event-Id: 7c9e6679-…
Lavesy-Signature: t=1767225600,v1=5f2b…c9
{
"id": "7c9e6679-…",
"type": "stock.low",
"created": "2026-11-03T08:15:00Z",
"company_id": "…",
"data": {
"article": { "id": "…", "sku": "KAB-NYM-3x1,5", "name": "NYM-J 3x1,5 mm²", "unit": "m" },
"quantity_on_hand": "40.000",
"min_stock": "50.000",
"target_stock": "200.000"
}
} Der Inhalt von data hängt vom Ereignis ab. Lavesy-Delivery ist pro Zustellung eindeutig, id pro Ereignis — nutze sie, um doppelte Zustellungen zu erkennen.
Signatur prüfen
Der Header Lavesy-Signature: t=<unix>,v1=<hex> enthält einen Zeitstempel und eine HMAC-SHA256-Signatur über "<t>.<roher Body>" mit dem Secret deines Endpunkts (whsec_…). Prüfe sie immer, vergleiche in konstanter Zeit und lehne Nachrichten ab, deren Zeitstempel mehr als 5 Minuten abweicht.
Python
import hashlib
import hmac
import time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
"""Prüft den Header Lavesy-Signature: t=<unix>,v1=<hex>."""
parts = dict(item.split("=", 1) for item in header.split(","))
timestamp = int(parts["t"])
if abs(time.time() - timestamp) > tolerance:
return False # zu alt: Replay-Schutz
signed = f"{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Node.js
const crypto = require('node:crypto')
function verify(rawBody, header, secret, tolerance = 300) {
// header: "t=<unix>,v1=<hex>"
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const timestamp = Number(parts.t)
if (Math.abs(Date.now() / 1000 - timestamp) > tolerance) return false
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.`)
.update(rawBody) // exakt die empfangenen Bytes, nicht neu serialisiert
.digest('hex')
const given = Buffer.from(parts.v1 || '', 'hex')
const wanted = Buffer.from(expected, 'hex')
return given.length === wanted.length && crypto.timingSafeEqual(given, wanted)
}Zustellung, Wiederholung, Abschaltung
- Antworte innerhalb von 10 Sekunden mit einem 2xx-Status; der Antwortinhalt wird ignoriert.
- Sonst wiederholt Lavesy die Zustellung mit wachsendem Abstand über bis zu rund 24 Stunden (1 Min., 5 Min., 30 Min., 2 Std., 6 Std., 15 Std.).
- Nach 5 aufeinanderfolgenden fehlgeschlagenen Zustellungen wird der Endpunkt abgeschaltet und die Admins bekommen eine E-Mail. Danach kannst du ihn in den Einstellungen wieder aktivieren und einzelne Zustellungen erneut senden.
- Weiterleitungen werden nicht verfolgt; interne oder private Adressen sind nicht erlaubt.
Beispiel: n8n
Bestand abrufen (HTTP Request)
Node: HTTP Request
Method: GET
URL: https://lavesy.de/api/v1/inventory/stock?limit=200
Authentication: Generic Credential Type → Header Auth
Name: Authorization
Value: Bearer lvs_DEIN_SCHLUESSEL
Pagination: Update a Parameter in Each Request
offset = {{ $pageCount * 200 }}
Complete When: {{ $response.body.items.length < 200 }}Webhook empfangen und Signatur prüfen
// Node "Webhook": Methode POST, Option "Raw Body" aktivieren.
// Danach ein "Code"-Node (JavaScript, Run Once for Each Item):
const crypto = require('crypto')
const secret = 'whsec_DEIN_SECRET' // besser: aus einer Credential/Variable
const header = $json.headers['lavesy-signature'] || ''
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const raw = Buffer.from($binary.data.data, 'base64') // Raw Body des Webhook-Nodes
const expected = crypto.createHmac('sha256', secret)
.update(parts.t + '.').update(raw).digest('hex')
const ok = parts.v1 && expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
if (!ok || Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) {
throw new Error('Ungültige Lavesy-Signatur')
}
return { json: JSON.parse(raw.toString('utf8')) } Zapier und Make funktionieren genauso: „Webhooks by Zapier" bzw. das Make-Modul „Custom webhook" als Empfänger, für API-Aufrufe ein HTTP-Modul mit dem Header Authorization.
API-Referenz
Alle Endpunkte, die ein API-Schlüssel aufrufen darf, mit dem nötigen Bereich. Das maschinenlesbare OpenAPI-Dokument liegt unter /api/v1/public/api-reference/openapi.json — z. B. zum Import in Postman oder n8n.
Die Referenz ist gerade nicht erreichbar — das OpenAPI-Dokument oben enthält dieselben Angaben.