Zum Hauptinhalt springen
API Dokumentation

REST API v1

Verbinden Sie BISpicy POS mit JTL-Wawi, Lexware, Shopify, DATEV oder Ihrem eigenen ERP-System. Produkte, Bestand, Verkäufe, Kunden und Webhooks - alles über eine API.

Zuletzt aktualisiert: 9. November 2026 · Aufbewahrung des Webhook-Zustellprotokolls (BIS ERP 0.0.0.1529) · 20. Oktober 2026: Schnellstart „API-Key erstellen“ an das neue Kundencenter angeglichen

https://bispicy.com/api/v1/

Übersicht

Die BISpicy POS REST API ermöglicht die Integration externer Systeme mit Ihrem Kassensystem. Synchronisieren Sie Produkte, Bestände, Verkaufsdaten und Kundendaten mit Ihrer bestehenden Infrastruktur.

Bidirektionaler Sync
Produkte, Kunden, Bestand
Echtzeit-Webhooks
Sofortige Benachrichtigungen
HMAC-SHA256
Signierte Schreibzugriffe

Für wen ist die API?

SystemTypischer Einsatz
JTL-WawiArtikel- und Bestandssync, Verkäufe als Aufträge importieren
LexwareBuchhaltungsexport, Kundenstammdaten
Shopify / WooCommerceOmnichannel-Bestand, Online-Bestellungen
DATEVTäglicher Kassenbericht, Buchungsdaten
Custom ERPEigene Integrationen, Automatisierungen

Technische Eckdaten

ParameterWert
Base URLhttps://bispicy.com/api/v1/
ProtokollHTTPS (TLS 1.3)
FormatJSON (UTF-8)
AuthentifizierungBearer Token + HMAC-SHA256
Rate Limits60 / 300 / 1.000 Requests/Min
Idempotency24h Cache für POST/PUT/PATCH

Schnellstart

In 3 Schritten zur ersten API-Abfrage:

1. API-Key erstellen

Erstellen Sie einen API-Key im BISpicy Kundencenter unter Anbindungen → API und Webhooks.

API-Schlüssel und Webhooks anlegen, ändern, erneuern, löschen und testen dürfen nur Benutzer mit der Rolle Administrator und der Lizenzinhaber des Betriebs (seit BIS ERP 0.0.0.1084). Andere Benutzer sehen die Liste und bekommen beim Ändern eine Meldung. Bestehende Schlüssel arbeiten unverändert weiter.

Credentials API-Key: bsk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 API-Secret: bss_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0
Wichtig: Key und Secret werden nur einmal angezeigt. Speichern Sie diese sicher.

2. Erster API-Call

cURL curl -X GET "https://bispicy.com/api/v1/products" \ -H "Authorization: Bearer bsk_live_a1b2c3d4..." \ -H "Content-Type: application/json"
Response 200 { "success": true, "data": [ { "id": "art_abc123", "sku": "BIS-001", "name": "Pad Thai", "selling_price": 12.90, "stock_quantity": 25 } ], "meta": { "page": 1, "per_page": 50, "total": 142 } }

3. Webhook registrieren

cURL curl -X POST "https://bispicy.com/api/v1/webhooks" \ -H "Authorization: Bearer bsk_live_a1b2c3d4..." \ -H "Content-Type: application/json" \ -d '{ "target_url": "https://eure-wawi.de/api/bispicy-webhook", "events": ["sale.completed", "inventory.updated"], "description": "JTL-Wawi Integration" }'

Authentifizierung

Lesende Zugriffe: Bearer Token

Für GET-Requests reicht der API-Key als Bearer Token:

HTTP GET /api/v1/products HTTP/1.1 Host: bispicy.com Authorization: Bearer bsk_live_a1b2c3d4... Accept: application/json

Schreibende Zugriffe: HMAC-SHA256 Signierung

Für POST, PUT, PATCH, DELETE ist zusätzlich eine HMAC-Signatur erforderlich.

Pflicht-Header

HeaderBeschreibung
AuthorizationBearer {api_key}
X-BISpicy-TimestampUnix-Timestamp (Sekunden)
X-BISpicy-Signaturev1={hmac_hex}
Idempotency-KeyEindeutige ID pro Request (UUID empfohlen)
Content-Typeapplication/json

Signatur berechnen

Python import hmac, hashlib, time, json, uuid timestamp = str(int(time.time())) body = json.dumps(payload, separators=(',', ':')) signed_content = f"{timestamp}.{body}" signature = hmac.new( api_secret.encode('utf-8'), signed_content.encode('utf-8'), hashlib.sha256 ).hexdigest() headers = { "Authorization": f"Bearer {api_key}", "X-BISpicy-Timestamp": timestamp, "X-BISpicy-Signature": f"v1={signature}", "Idempotency-Key": str(uuid.uuid4()) }
Sicherheit: API-Secret niemals im Frontend oder Client-Code verwenden. Nur serverseitig signieren.

Server-seitige Verifizierung

Der Server prüft bei jedem Request:

  1. API-Key vorhanden und aktiv? → 401
  2. API-Key nicht abgelaufen? → 401
  3. IP in Whitelist (wenn konfiguriert)? → 403
  4. Scope ausreichend? → 403
  5. Timestamp max. 5 Minuten alt? → 401 (Replay-Schutz)
  6. HMAC-Signatur korrekt? (nur Schreibzugriffe) → 401
  7. Rate Limit eingehalten? → 429

Scopes & Berechtigungen

Bei der API-Key-Erstellung werden Scopes festgelegt. Vergeben Sie nur die Berechtigungen, die tatsächlich benötigt werden (Principle of Least Privilege).

ScopeErlaubt
products:readArtikel und Kategorien lesen
products:writeArtikel erstellen, ändern, löschen
orders:readVerkäufe und Bestellungen lesen
inventory:readBestände lesen
inventory:writeBestände anpassen
customers:readKundendaten lesen
customers:writeKunden erstellen, ändern, löschen
reports:readKassenschlüsse, Z-Bons, Steuerberichte
webhooks:manageWebhooks erstellen, ändern, löschen
settings:readGeschäftseinstellungen lesen
Beispiel: Ein reiner Bestands-Sync braucht nur products:read + inventory:read + inventory:write.

Endpoints-Übersicht

MethodePfadScopeBeschreibung
GET/productsproducts:readProduktliste (paginiert, Filter)
GET/products/{id}products:readEinzelnes Produkt
POST/productsproducts:writeProdukt erstellen
PUT/products/{id}products:writeProdukt aktualisieren
DEL/products/{id}products:writeProdukt löschen (Soft-Delete)
POST/products/batchproducts:writeBatch-Upsert (max. 500)
Kategorien
GET/categoriesproducts:readKategorien auflisten (hierarchisch)
GET/categories/{id}products:readEinzelne Kategorie
POST/categoriesproducts:writeKategorie erstellen
PUT/categories/{id}products:writeKategorie aktualisieren
DEL/categories/{id}products:writeKategorie löschen
Bestand
GET/inventoryinventory:readBestandslevel (low_stock Filter)
POST/inventory/adjustmentsinventory:writeBestandskorrektur (set/add/subtract)
Verkäufe (Read-Only)
GET/ordersorders:readVerkaufsliste
GET/orders/{id}orders:readEinzelner Verkauf mit Positionen
Kunden
GET/customerscustomers:readKundenliste
GET/customers/{id}customers:readEinzelner Kunde
POST/customerscustomers:writeKunde erstellen
PUT/customers/{id}customers:writeKunde aktualisieren
DEL/customers/{id}customers:writeDSGVO-Anonymisierung
Webhooks
GET/webhookswebhooks:manageWebhook-Liste
POST/webhookswebhooks:manageWebhook erstellen
PUT/webhooks/{id}webhooks:manageWebhook aktualisieren
DEL/webhooks/{id}webhooks:manageWebhook löschen
POST/webhooks/{id}/testwebhooks:manageTest-Event senden

Produkte

GET /products

Produktliste mit Pagination und Filtern.

ParameterTypBeschreibung
pageintSeitennummer (Default: 1)
per_pageintErgebnisse pro Seite (Default: 50, Max: 200)
sinceISO 8601Nur Änderungen seit Zeitstempel (Delta-Sync)
activeboolNur aktive Produkte filtern
category_idstringNach Kategorie filtern
skustringExakte Suche nach Artikelnummer (SKU)
eanstringExakte Suche nach EAN/GTIN-Barcode
searchstringVolltextsuche in Name, SKU, EAN

POST /products

Neues Produkt erstellen.

Request { "sku": "BIS-042", "name": "Tom Yum Suppe", "selling_price": 8.50, "tax_rate": 19, "category_id": "cat_abc123", "stock_quantity": 50 }

POST /products/batch

Bis zu 500 Produkte in einem Request erstellen oder aktualisieren.

Request { "items": [ { "action": "upsert", "sku": "BIS-001", "name": "Pad Thai", "selling_price": 12.90 }, { "action": "upsert", "sku": "BIS-002", "name": "Green Curry", "selling_price": 11.50 } ] }

Kategorien

Kategorien verwalten die hierarchische Produktstruktur. Kompatibel mit JTL-Wawi, Lexware und anderen ERP-Systemen.

GET /categories

Kategorien auflisten mit optionalen Filtern.

ParameterTypBeschreibung
sinceISO 8601Nur geänderte Kategorien
activeboolNur aktive/inaktive Kategorien
parent_idUUIDUnterkategorien einer bestimmten Kategorie
rootboolNur Hauptkategorien (ohne Elternkategorie)

POST /categories

Neue Kategorie erstellen.

Request { "name": "Getränke", "parent_id": null, "sort_order": 1, "description": "Alle Getränkeprodukte" }

PUT /categories/{id}

Kategorie aktualisieren. Nur übergebene Felder werden geändert.

DEL /categories/{id}

Kategorie löschen. Produkte werden automatisch aus der Kategorie entfernt. Kategorien mit Unterkategorien können nicht gelöscht werden.

Bestandsverwaltung

GET /inventory

Aktuelle Bestandslevel aller Produkte. Mit low_stock=true nur Artikel unter Mindestbestand.

POST /inventory/adjustments

Bestandskorrekturen durchführen.

TypBeschreibung
setAbsolut setzen (Inventur)
addHinzufügen (Wareneingang)
subtractAbziehen (Schwund, Korrektur)
Request { "adjustments": [ { "product_id": "art_abc123", "type": "add", "quantity": 50, "reason": "Wareneingang" }, { "product_id": "art_def456", "type": "set", "quantity": 0, "reason": "Inventur" } ] }

Verkäufe

Nur lesend: Verkäufe können über die API nicht erstellt oder geändert werden, denn jeder Verkauf wird an der Kasse von der TSE signiert. Verkäufe werden ausschließlich über die POS-App erstellt.

GET /orders

Verkaufsliste mit Filtern: since (Delta-Sync), until, device_id.

GET /orders/{id}

Einzelner Verkauf mit allen Positionen, MwSt-Aufschlüsselung, Zahlungsart und TSE-Daten.

Response 200 { "id": "sale_xyz789", "receipt_number": "2026-00142", "total_gross": 25.80, "payment_method": "CASH", "items": [ { "name": "Pad Thai", "quantity": 2, "unit_price": 12.90 } ], "tse": { "transaction_number": 4217, "signature": "MEUCIQDx...", "serial_number": "abc123def..." } }

Kunden

Vollständiges CRUD für Kundenstammdaten.

POST /customers

Request { "first_name": "Max", "last_name": "Mustermann", "email": "max@beispiel.de", "type": "business", "company": "Mustermann GmbH", "vat_id": "DE123456789" }

DEL /customers/{id}

DSGVO Art. 17: DELETE anonymisiert die Kundendaten (Name, E-Mail, Adresse werden überschrieben). Verkaufshistorie bleibt erhalten, aber ohne Personenbezug.

Webhooks

Echtzeit-Benachrichtigungen per HTTP POST wenn auf der Kasse etwas passiert.

Verfügbare Events

EventAuslöser
sale.completedVerkauf an der Kasse abgeschlossen
sale.refundedVerkauf erstattet (Rückgabe oder Storno)
order.createdNeuer Auftrag angelegt
order.status_changedStatus eines Auftrags geändert
order.partially_shippedAuftrag teilweise versendet
order.shippedAuftrag vollständig versendet
product.createdNeues Produkt angelegt
product.updatedProdukt geändert
product.deletedProdukt gelöscht
category.createdNeue Kategorie angelegt
category.updatedKategorie geändert
category.deletedKategorie gelöscht
customer.createdNeuer Kunde angelegt
customer.updatedKundendaten geändert
customer.deletedKunde anonymisiert (DSGVO)
inventory.updatedBestand geändert
inventory.transferWare zwischen Lagerplätzen umgebucht (Lager-App oder BIS ERP)
voucher.createdGutschein angelegt
voucher.updatedGutschein geändert
voucher.redeemedGutschein eingelöst
voucher.cancelledGutschein storniert
*Alle Ereignisse; auch Präfixe wie order.*

Artikel-, Kategorie-, Kunden-, Gutschein- und Bestandsereignisse (product.*, category.*, customer.*, voucher.*, inventory.updated) melden Änderungen, die über diese API gemacht werden. Verkäufe der Kasse, neue Aufträge und Umbuchungen meldet BIS ERP unabhängig davon, wo sie entstehen; Status- und Versandmeldungen (order.status_changed, order.partially_shipped, order.shipped) gibt es für Aufträge, die über diese API angelegt wurden.

Signatur-Verifizierung

Jeder Webhook wird mit HMAC-SHA256 signiert. Verifizieren Sie die Signatur im Header X-BISpicy-Webhook-Signature:

Python import hmac, hashlib, time def verify_webhook(payload_body, signature_header, secret): timestamp, received_sig = signature_header.split(",") ts = timestamp.split("=")[1] sig = received_sig.split("=", 1)[1] if abs(time.time() - int(ts)) > 300: # älter als 5 Minuten: Wiederholung abweisen return False expected = hmac.new( secret.encode('utf-8'), f"{ts}.{payload_body}".encode('utf-8'), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, sig)

Retry-Verhalten

Bei Fehlern (kein 2xx-Response) wird die Zustellung mit exponentiellem Backoff wiederholt:

VersuchWartezeit
11 Minute
25 Minuten
330 Minuten
42 Stunden
56 Stunden
612 Stunden
724 Stunden
8 (letzter)Dead Letter Queue
Circuit Breaker: Nach 5 aufeinanderfolgenden Fehlern wird die Zustellung für 1 Stunde pausiert. Fehlgeschlagene Deliveries landen in der Dead-Letter-Queue und können manuell wiederholt werden.
Altersgrenze und Zieladresse: Eine fehlgeschlagene Zustellung wird nach wachsenden Abständen (1 Minute, 5 Minuten, 30 Minuten und weiter) automatisch wiederholt; geprüft wird etwa alle 10 Minuten. Eine Zustellung, die älter als 72 Stunden ist, wird nicht mehr wiederholt, sondern aufgegeben und landet in der Dead-Letter-Queue. Das Ziel muss eine öffentlich erreichbare https-Adresse sein: Interne, lokale oder reservierte Adressen werden beim Speichern mit einem Fehler abgelehnt, Weiterleitungen werden nicht verfolgt. Ein manuell wiederholter Dead Letter bekommt noch bis zu drei Versuche.
Aufbewahrung des Zustellprotokolls: Das Protokoll Ihrer Webhook-Zustellungen enthält je nach Ereignis auch Kundendaten, etwa bei customer.created. Seit BIS ERP 0.0.0.1529 wird es automatisch gelöscht: erfolgreich zugestellte Meldungen nach 30 Tagen, nicht zustellbare (Dead Letter) nach 90 Tagen. Zustellungen, die noch auf einen weiteren Versuch warten, bleiben stehen. Einen Dead Letter wiederholen Sie deshalb innerhalb von 90 Tagen.

Rate Limiting

TierRequests/MinuteRequests/Tag
Basic605.000
Standard30050.000
Premium1.000200.000

Jede Response enthält Rate-Limit-Header:

Response Headers X-RateLimit-Limit: 300 X-RateLimit-Remaining: 247 X-RateLimit-Reset: 1709390460

Bei Überschreitung: 429 Too Many Requests mit Retry-After Header.

Fehlerbehandlung

HTTP-Statuscodes

CodeBedeutung
200OK - Erfolgreicher GET, PUT, PATCH, DELETE
201Created - Erfolgreicher POST
400Bad Request - Ungültiges JSON, fehlende Pflichtfelder
401Unauthorized - API-Key ungültig/abgelaufen, HMAC falsch
403Forbidden - Fehlender Scope, IP nicht erlaubt
404Not Found - Ressource existiert nicht
409Conflict - Duplikat (SKU/EAN), Idempotency-Conflict; PUT /products/{id} mit is_active: offene_auftraege (Deaktivieren mit offenen Aufträgen, open_order_count) bzw. ean_vergeben (Aktivieren, EAN trägt inzwischen ein anderes aktives Produkt)
422Unprocessable - Validierungsfehler
429Too Many Requests - Rate Limit überschritten
500Server Error - Bitte mit request_id melden

Error Response Format

JSON { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Ungültige Eingabedaten", "details": [ { "field": "selling_price", "message": "Muss eine positive Zahl sein" } ] }, "request_id": "req_a1b2c3d4e5f6" }

Error Codes

CodeHTTPBeschreibung
AUTHENTICATION_REQUIRED401Kein Authorization-Header
INVALID_API_KEY401API-Key ungültig oder deaktiviert
INVALID_SIGNATURE401HMAC-Signatur stimmt nicht
TIMESTAMP_EXPIRED401Timestamp älter als 5 Minuten
INSUFFICIENT_SCOPE403API-Key hat nicht den nötigen Scope
IP_NOT_ALLOWED403IP nicht in Whitelist
VALIDATION_ERROR422Feldvalidierung fehlgeschlagen
RATE_LIMIT_EXCEEDED429Rate Limit überschritten

Code-Beispiele

Python

Python import hmac, hashlib, time, json, uuid, requests API_KEY = "bsk_live_a1b2c3d4..." API_SECRET = "bss_a1b2c3d4..." BASE_URL = "https://bispicy.com/api/v1" def api_request(method, endpoint, payload=None): timestamp = str(int(time.time())) body = json.dumps(payload, separators=(',',':')) if payload else "" signed = f"{timestamp}.{body}" sig = hmac.new(API_SECRET.encode(), signed.encode(), hashlib.sha256).hexdigest() headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "X-BISpicy-Timestamp": timestamp, "X-BISpicy-Signature": f"v1={sig}", "Idempotency-Key": str(uuid.uuid4()) } resp = requests.request(method, f"{BASE_URL}{endpoint}", headers=headers, json=payload) return resp.json() # Produkte abrufen products = api_request("GET", "/products?per_page=100") # Bestand korrigieren api_request("POST", "/inventory/adjustments", { "adjustments": [{"product_id": "art_abc", "type": "add", "quantity": 50}] })

PHP

PHP <?php $apiKey = 'bsk_live_a1b2c3d4...'; $apiSecret = 'bss_a1b2c3d4...'; $baseUrl = 'https://bispicy.com/api/v1'; function apiRequest($method, $endpoint, $payload = null) { global $apiKey, $apiSecret, $baseUrl; $timestamp = (string) time(); $body = $payload ? json_encode($payload, JSON_UNESCAPED_UNICODE) : ''; $signature = hash_hmac('sha256', "{$timestamp}.{$body}", $apiSecret); $ch = curl_init("{$baseUrl}{$endpoint}"); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", "Content-Type: application/json", "X-BISpicy-Timestamp: {$timestamp}", "X-BISpicy-Signature: v1={$signature}", "Idempotency-Key: " . bin2hex(random_bytes(16)) ], CURLOPT_POSTFIELDS => $body ?: null ]); return json_decode(curl_exec($ch), true); } // Produkte abrufen $products = apiRequest('GET', '/products'); // Neues Produkt erstellen apiRequest('POST', '/products', [ 'sku' => 'MEIN-001', 'name' => 'Neuer Artikel', 'selling_price' => 9.99 ]);

Node.js

JavaScript import crypto from 'crypto'; const API_KEY = 'bsk_live_a1b2c3d4...'; const API_SECRET = 'bss_a1b2c3d4...'; const BASE_URL = 'https://bispicy.com/api/v1'; async function apiRequest(method, endpoint, payload) { const timestamp = Math.floor(Date.now() / 1000).toString(); const body = payload ? JSON.stringify(payload) : ''; const sig = crypto.createHmac('sha256', API_SECRET) .update(`${timestamp}.${body}`).digest('hex'); const res = await fetch(`${BASE_URL}${endpoint}`, { method, headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', 'X-BISpicy-Timestamp': timestamp, 'X-BISpicy-Signature': `v1=${sig}`, 'Idempotency-Key': crypto.randomUUID() }, body: body || undefined }); return res.json(); } // Produkte abrufen const products = await apiRequest('GET', '/products'); // Webhook registrieren await apiRequest('POST', '/webhooks', { target_url: 'https://mein-server.de/webhook', events: ['sale.completed', 'inventory.updated'] });

cURL

Bash # Produkte auflisten curl -X GET "https://bispicy.com/api/v1/products?per_page=100" \ -H "Authorization: Bearer bsk_live_a1b2c3d4..." # Einzelnes Produkt abrufen curl -X GET "https://bispicy.com/api/v1/products/art_abc123" \ -H "Authorization: Bearer bsk_live_a1b2c3d4..." # Verkäufe seit gestern abrufen curl -X GET "https://bispicy.com/api/v1/orders?since=2026-03-01T00:00:00Z" \ -H "Authorization: Bearer bsk_live_a1b2c3d4..."