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.

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 API & Integrationen.

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 (KassenSichV-Konformität). 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": "[email protected]", "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 abgeschlossen
sale.cancelledStornierung durchgeführt
inventory.adjustedBestandsänderung (Verkauf, Import, Korrektur)
product.createdNeues Produkt angelegt
product.updatedProdukt geändert
product.deletedProdukt gelöscht
customer.createdNeuer Kunde angelegt
customer.updatedKundendaten geändert
customer.deletedKunde anonymisiert (DSGVO)
cash_closing.completedKassenschluss (Z-Bon) erstellt

Signatur-Verifizierung

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

Python import hmac, hashlib def verify_webhook(payload_body, signature_header, secret): timestamp, received_sig = signature_header.split(",") ts = timestamp.split("=")[1] sig = received_sig.split("=", 1)[1] 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.

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
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.adjusted'] });

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..."