Schnittstelle (API und Webhooks)

Für Ihren Shop, Ihre Warenwirtschaft oder Ihren Programmierer: Rechnungen und Kunden aus RCDisi lesen und anlegen — und sofort erfahren, wenn eine Rechnung ausgestellt oder bezahlt ist. Fassung 1.

Einrichten (Inhaber oder Administrator): Verwaltung → Automatik → Schnittstelle (API und Webhooks).

Ein API-Schlüssel ist wie ein Passwort. Nicht in eine Webseite, eine App oder ein öffentliches Verzeichnis schreiben — nur auf Ihrem Server. Nur die Rechte geben, die das Programm braucht. Wird ein Schlüssel bekannt: sofort widerrufen (gleicher Ort) und einen neuen erzeugen. Wer die Daten über die Schnittstelle bekommt, verarbeitet sie in Ihrem Auftrag — dafür brauchen Sie mit ihm ggf. einen eigenen Vertrag zur Auftragsverarbeitung.

1. Anmelden

Jede Anfrage trägt den Schlüssel im Kopf:

Authorization: Bearer rcd_IhrSchluessel…
Rechterlaubt
rechnungen:lesenRechnungen lesen (mit Stand und PDF)
rechnungen:schreibenRechnungen ausstellen
kunden:lesenKunden lesen
kunden:schreibenKunden anlegen

2. Die Wege

WegRechtwas
GET /ich—Ihr Betrieb und die Rechte dieses Schlüssels (zum Ausprobieren)
GET /rechnungenrechnungen:lesenKundenrechnungen und Direktabrechnungen, älteste zuerst. Filter: seit=2026-11-01 (angelegt ab), bezahlt=true|false, limit=1…200 (Vorgabe 50), nach=<id> (nächste Seite)
GET /rechnungen/{id}rechnungen:leseneine Rechnung
GET /rechnungen/{id}/pdfrechnungen:lesendas PDF (nur für Rechnungen, die in RCDisi geschrieben wurden)
POST /rechnungenrechnungen:schreibeneine Rechnung ausstellen — wie „Rechnung schreiben": Nummer, PDF mit E-Rechnung. Sie wird nicht verschickt.
GET /kundenkunden:lesenKunden, älteste zuerst. suche= (Name, Kundennummer, E-Mail), limit, nach
GET /kunden/{id}kunden:lesenein Kunde
POST /kundenkunden:schreibeneinen Kunden anlegen

Seiten: Steht in der Antwort "weiter": "<id>", gibt es mehr — dieselbe Anfrage mit nach=<id>. Bei "weiter": null ist alles da.

Beispiele

# Ausprobieren
curl -H "Authorization: Bearer $RCDISI_SCHLUESSEL" https://rcdisi.de/api/v1/ich

# Offene Rechnungen
curl -H "Authorization: Bearer $RCDISI_SCHLUESSEL" "https://rcdisi.de/api/v1/rechnungen?bezahlt=false&limit=100"

# Einen Kunden anlegen
curl -X POST https://rcdisi.de/api/v1/kunden \
  -H "Authorization: Bearer $RCDISI_SCHLUESSEL" -H "Content-Type: application/json" \
  -H "Idempotency-Key: shop-kunde-4711" \
  -d '{"name":"Muster GmbH","nummer":"K-4711","email":"buchhaltung@muster.example","strasse":"Hauptstraße 1","plzOrt":"12345 Musterstadt"}'

# Eine Rechnung ausstellen
curl -X POST https://rcdisi.de/api/v1/rechnungen \
  -H "Authorization: Bearer $RCDISI_SCHLUESSEL" -H "Content-Type: application/json" \
  -H "Idempotency-Key: shop-bestellung-10023" \
  -d '{"kunde":{"name":"Muster GmbH","nummer":"K-4711","strasse":"Hauptstraße 1","plzOrt":"12345 Musterstadt","email":"buchhaltung@muster.example"},
       "datum":"2026-11-03","zielTage":14,"basis":"netto","betreff":"Bestellung 10023",
       "positionen":[{"bezeichnung":"Beratung","menge":2,"einheit":"Std.","einzel":90,"steuersatz":19}]}'

Eine Rechnung ausstellen — die Angaben

Feld
kundename, strasse, plzOrt (Pflicht), dazu nummer, zusatz, land, email, ustid. Gibt es die Kundennummer schon, kommt die Rechnung in diese Akte; ohne Nummer vergibt RCDisi die nächste freie.
positionenje Zeile bezeichnung, menge, einzel (Einzelpreis in Euro), steuersatz (19, 7 oder 0), dazu einheit, beschreibung
basisnetto oder brutto — gilt der Einzelpreis mit oder ohne Umsatzsteuer (Vorgabe: Ihre Einstellung)
datum, leistungVon, leistungBiswie 2026-11-03 (Vorgabe: heute)
zielTageZahlungsziel in Tagen (Vorgabe: Ihre Einstellung)
betreff, einleitung, schlussTexte auf der Rechnung

Fehlt eine Pflichtangabe, antwortet RCDisi mit 400 und sagt, was fehlt — es gibt dann keine Nummer und keine Rechnung. Angebote, Abos, Abschlags- und Schlussrechnungen, Korrekturen und Gutschriften gehen über das Programm, nicht über die Schnittstelle.

Doppelt geschickt? Idempotency-Key

Schicken Sie bei POST einen eigenen, eindeutigen Wert mit (z. B. Ihre Bestellnummer). Kommt dieselbe Anfrage innerhalb von 24 Stunden noch einmal — etwa weil die Verbindung abriss —, bekommen Sie die erste Antwort zurück (mit "wiederholt": true) und es entsteht keine zweite Rechnung.

Was zurückkommt

{ "id": "…", "nummer": "RE-2026-0042", "art": "kundenrechnung", "kunde": "Muster GmbH", "kundennummer": "K-4711",
  "datum": "2026-11-03", "faellig": "2026-11-17", "betrag": 214.2, "waehrung": "EUR", "offen": 214.2, "bezahlt": false,
  "bezahltAm": null, "storniert": false, "mahnstufe": 0, "inRcdisiGeschrieben": true, "pdf": true }

{ "id": "…", "kundennummer": "K-4711", "name": "Muster GmbH", "email": "buchhaltung@muster.example", "telefon": "", "anschrift": "…" }

Fehler und Grenzen

Statusbedeutet
400Angabe fehlt oder stimmt nicht — {"error": "…"} sagt, was
401kein oder ein widerrufener Schlüssel
403dem Schlüssel fehlt das Recht, oder der Zugang des Betriebs ist beendet/gesperrt
404gibt es nicht (oder gehört nicht zu Ihrem Betrieb)
409gibt es schon (z. B. die Kundennummer) oder derselbe Idempotency-Key für einen anderen Weg
423der Vertrag ist zu Ende — nur noch lesen
429mehr als 120 Anfragen in einer Minute mit diesem Schlüssel — kurz warten

3. Webhooks — RCDisi meldet sich bei Ihnen

Bei jedem bestellten Ereignis schickt RCDisi ein POST mit JSON an Ihre Adresse. Antworten Sie innerhalb von 10 Sekunden mit einem Status 2xx — dann gilt die Meldung als angekommen. Umleitungen (3xx) folgt RCDisi nicht.

Ereigniswanndaten
rechnung.ausgestellteine Rechnung wurde in RCDisi geschrieben (auch über die Schnittstelle)id, nummer, art, datum, bruttoCent, betrag, waehrung, kunde, kundennummer
rechnung.storniertsie wurde storniertdasselbe; nummer ist die des Stornos, stornoZu die der Rechnung, id die Rechnung
rechnung.bezahltsie gilt als bezahlt (Kontoauszug, Online-Zahlung oder von Hand)id, nummer, betrag, waehrung, kunde, kundennummer, bezahltAm, vonHand
kunde.angelegtein Kunde wurde angelegtid, kundennummer, name, email
angebot.angenommenein Angebot wurde angenommenid, nummer, art, kunde
probe.ping„Probe senden" in der Verwaltungnachricht
POST https://ihr-programm.example/rcdisi
Content-Type: application/json; charset=utf-8
X-RCDisi-Ereignis: rechnung.bezahlt
X-RCDisi-Zustellung: 0b7e…   (dieselbe wie "id" unten)
X-RCDisi-Signatur: t=1793700000,v1=5f2c…

{"id":"0b7e…","ereignis":"rechnung.bezahlt","zeit":"2026-11-03T10:15:00Z",
 "daten":{"id":"…","nummer":"RE-2026-0042","betrag":214.2,"waehrung":"EUR","kunde":"Muster GmbH","kundennummer":"K-4711",
          "bezahltAm":"2026-11-03T10:14:58.123+00:00","vonHand":false}}

Kam nichts an (kein 2xx, keine Antwort), versucht RCDisi es nach 1 Minute, 5 Minuten, 30 Minuten, 2 Stunden, 6 Stunden und 24 Stunden noch einmal. Darum kann eine Meldung auch zweimal kommen — merken Sie sich die id und verarbeiten Sie jede nur einmal. Nach 20 Fehlschlägen in Folge schaltet RCDisi den Webhook aus und sagt es Ihnen in der Glocke; einschalten in der Verwaltung.

Die Signatur prüfen

X-RCDisi-Signatur: t=<Sekunden seit 1970>,v1=<HMAC-SHA256> — der HMAC wird mit Ihrem Geheimnis über t + "." + der unveränderte Inhalt gebildet (hexadezimal). Prüfen Sie den rohen Inhalt, bevor Sie ihn als JSON lesen, und weisen Sie Zeitstempel ab, die älter als 5 Minuten sind.

Node.js
const crypto = require('crypto');
function echt(geheimnis, kopf, roh) {   // roh: der Inhalt als Text, genau wie empfangen
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(kopf || '');
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
  const soll = crypto.createHmac('sha256', geheimnis).update(m[1] + '.' + roh).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(soll, 'hex'), Buffer.from(m[2], 'hex'));
}
PHP
function echt($geheimnis, $kopf, $roh) {
  if (!preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $kopf, $m)) return false;
  if (abs(time() - (int)$m[1]) > 300) return false;
  return hash_equals(hash_hmac('sha256', $m[1] . '.' . $roh, $geheimnis), $m[2]);
}
$roh = file_get_contents('php://input');
$ok = echt(getenv('RCDISI_GEHEIMNIS'), $_SERVER['HTTP_X_RCDISI_SIGNATUR'] ?? '', $roh);
Python
import hashlib, hmac, re, time
def echt(geheimnis: str, kopf: str, roh: bytes) -> bool:
    m = re.fullmatch(r't=(\d+),v1=([0-9a-f]{64})', kopf or '')
    if not m or abs(time.time() - int(m.group(1))) > 300:
        return False
    soll = hmac.new(geheimnis.encode(), m.group(1).encode() + b'.' + roh, hashlib.sha256).hexdigest()
    return hmac.compare_digest(soll, m.group(2))

Welche Adressen gehen

Nur https:// auf Port 443, ohne Benutzername und Passwort in der Adresse, und nur Adressen im öffentlichen Netz — nichts, was ins eigene Netz oder auf den Rechner selbst zeigt (das prüft RCDisi beim Anlegen und vor jeder Meldung).

RCDisi · Datenformate · Impressum · Datenschutz