Technische Beschreibung

Exportschnittstelle

Diese Seite beschreibt, wie ein Kunde oder ein von ihm beauftragter Zielanbieter das vollständige Exportpaket abruft. Sie richtet sich an Entwicklerinnen und Entwickler. Die allgemeinverständliche Fassung steht unter Datenexport und Anbieterwechsel. Formatfassung: 1.0.

Was ein Export ist — und was nicht

Ein Export erstellt eine Kopie. Er beendet keinen Vertrag, kündigt nichts und löst keine Löschung aus. Anbieterwechsel und Löschung sind eigene Vorgänge, die der Kunde ausdrücklich auswählt. Ein Abruf über diese Schnittstelle verändert am Konto nichts außer dem Abrufzähler des benutzten Zugangs.

Authentifizierung

Es gibt keinen unauthentifizierten Zugang. Zwei Wege sind möglich:

  • Sitzung — der angemeldete Kontoinhaber lädt direkt herunter.
  • Export-Token — der Kontoinhaber erzeugt einen Zugang und gibt ihn an den Zielanbieter weiter. Das Token ist 32 Byte Zufall (base64url) und wird serverseitig nur als SHA-256 gespeichert; es wird genau einmal ausgeliefert und lässt sich nicht erneut anzeigen.

Ein Token ist ausschließlich an das Konto gebunden, das es erzeugt hat. Es gibt keinen Parameter, mit dem sich ein anderes Konto ansprechen ließe.

Ablauf

# 1) Zugang erzeugen (als angemeldeter Kontoinhaber)
curl -X POST https://kurzhand.de/api/export/jobs \
  -H "content-type: application/json" \
  -d '{"gueltigkeit_minuten": 60, "max_abrufe": 3}'

# Antwort (gekürzt) — das Token erscheint nur hier:
# { "success": true, "data": {
#     "id": "…", "token": "…", "download_url": "$SUPABASE/functions/v1/export-v2?aktion=manifest&token=…",
#     "laeuft_ab_am": "2026-08-03T12:00:00.000Z", "max_abrufe": 3 } }

# 2) Paket abrufen (Zielanbieter, ohne Konto bei kurzhand)
# 1. Manifest holen (eroeffnet die Sitzung)
curl -s -H "x-export-token: $TOKEN" \
  "$SUPABASE/functions/v1/export-v2?aktion=manifest" > manifest.json

# 2. Je Bereich die Seiten abrufen, der Fortsetzung folgen
curl -s -D kopf.txt -H "x-export-token: $TOKEN" \
  "$SUPABASE/functions/v1/export-v2?aktion=seite&bereich=kunden" > kunden.ndjson
#    Fortsetzung: X-Export-Naechster-Cursor aus kopf.txt als &cursor=… anhaengen

# 3. Dateiliste holen und jede Datei direkt vom Dateispeicher laden
curl -s -H "x-export-token: $TOKEN" \
  "$SUPABASE/functions/v1/export-v2?aktion=dateien&bucket=rechnungen" > dateien.json

# 3) Zugang widerrufen, sobald der Wechsel abgeschlossen ist
curl -X POST https://kurzhand.de/api/export/jobs/widerruf \
  -H "content-type: application/json" -d '{}'

Endpunkte

EndpunktAuthentifizierungWirkung
POST /api/export/jobsSitzung des KontoinhabersLegt einen Exportzugang an und liefert das Token genau einmal zurück. Optionaler Body: gueltigkeit_minuten (5–1440, Standard 60), max_abrufe (1–10, Standard 3).
GET /api/export/jobsSitzung des KontoinhabersListet die eigenen Exportzugänge mit Status, Ablauf, Abrufzahl, Größe und Prüfsumme. Ohne Tokens.
POST /api/export/jobs/widerrufSitzung des KontoinhabersWiderruft einen Zugang (Body: {"id": "…"}) oder ohne Body alle offenen Zugänge. Wirkt sofort.
GET <supabase>/functions/v1/export-v2?aktion=manifestExport-Token oder SitzungVerzeichnis des Exports: Bereiche, Datensatzzahlen, Seitengrösse, Dateilisten, Grenzen. Eröffnet zugleich die Exportsitzung und zählt einen Abruf.
GET …?aktion=seite&bereich=…&cursor=…Export-Token oder SitzungEine Seite eines Datenbereichs als NDJSON. Antwortköpfe nennen Zeilenzahl, SHA-256 der Seite und die Fortsetzungsmarke.
GET …?aktion=dateien&bucket=…&cursor=…Export-Token oder SitzungDateiliste eines Bereichs mit SHA-256, Grösse und einer wenige Minuten gültigen Downloadadresse je Datei. Die Datei selbst kommt direkt vom Dateispeicher.

Aufbau des Pakets

Es gibt zwei Wege, und beide liefern denselben Bestand.

Im Konto setzt der Browser ein ZIP-Archiv ohne Komprimierung zusammen (Methode STORE, Dateinamen in UTF-8). Die Daten kommen dabei aus der Datenbank, die Dateien aus dem Dateispeicher — ohne Umweg über einen Anwendungsserver.

kurzhand-export-JJJJ-MM-TT.zip
├── manifest.json       Verzeichnis: Bereiche, Zahlen, Grenzen
├── daten/<bereich>.ndjson   je Bereich eine Zeile je Datensatz
├── pruefsummen.json    SHA-256 je Datei
├── dateien/logos/     Firmenlogo
├── dateien/angebote/  Angebots-PDF
├── dateien/rechnungen/ Rechnungs-PDF und E-Rechnungs-XML
├── dateien/abnahmen/  Abnahmefotos
└── dateien/auftragsfotos/ Auftragsfotos

Über die Schnittstelle gibt es kein Archiv: Das Paket besteht aus dem Manifest, den seitenweise abrufbaren Bereichen als NDJSON und den Dateien, die über kurzlebige Downloadadressen direkt aus dem Dateispeicher kommen. Zusammen sind sie der vollständige Export. Genau so steht es auch in Anhang B § 5a Abs. 2a.

Die Dateipfade entsprechen den ursprünglichen Speicherpfaden ohne die vorangestellte Kontokennung — der Name trägt damit keine Kontobezüge.

Aufbau von data.json

{
  "exportiert_am": "2026-08-03T10:00:00.000Z",
  "format_version": "1.0",
  "nutzer": { "id": "…", "email": "…" },
  "hinweise": {
    "vollstaendigkeit": "…",
    "entfernte_felder": "…",
    "nicht_enthalten": { … },
    "unvollstaendige_bereiche": [],
    "fehlerhafte_bereiche": []
  },
  "bereiche": {
    "kunden": [ { … } ],
    "angebote": [ { … } ],
    …
  }
}

Die Felder innerhalb eines Bereichs entsprechen eins zu eins den Spalten der jeweiligen Tabelle. Sie sind nicht umbenannt — ein Zielanbieter sieht die Rohstruktur und muss nicht raten, welche Umbenennung was bedeutet.

Datenkategorien

Das Paket enthält die folgenden Bereiche, soweit im Konto vorhanden:

Bereich in data.jsonQuelle
profilprofiles
kundenkunden
angeboteangebote
rechnungenrechnungen
rechnungs_zahlungenrechnungs_zahlungen
leistungenleistungen
materialienmaterialien
vorlagenvorlagen
bausteinebausteine
dokument_vorlagendokument_vorlagen
auftraegeauftraege
activityactivity
erinnerungenreminders
agent_tasksagent_tasks
abonnementssubscriptions
angebot_signaturenangebot_signaturen
rechnungsnummern_nachweisinvoice_numbers
korrekturbelegekorrekturbelege
rechnung_abschlaegerechnung_abschlaege
serienrechnungenrecurring_invoices
auftrag_serienauftrag_serien
auftrag_fotosauftrag_fotos
abnahmenabnahmen
abnahme_maengelabnahme_maengel
abnahme_fotosabnahme_fotos
abnahme_signaturenabnahme_signaturen
zeiteintraegezeiteintraege
zeiteintrag_aenderungenzeiteintrag_aenderungen
ki_guthabenai_credits
ki_nutzungai_usage
ki_bestaetigungenai_confirm_tokens_used
ki_provenienzki_provenienz
team_mitgliederteam_mitglieder
team_mitglied_episodenteam_mitglied_episoden
team_audit_logteam_audit_log
team_einladungenteam_einladungen
urlaubsanspruchurlaubsanspruch
abwesenheitenabwesenheiten
zeit_korrektur_antraegezeit_korrektur_antraege
auftrag_berichteauftrag_berichte
dokument_freischaltungendocument_unlocks
rechtstext_einbeziehungenlegal_acceptance_log
unternehmerbestaetigungenb2b_bestaetigungen
support_ticketssupport_tickets
support_ticket_nachrichtensupport_ticket_messages
befragung_antwortenkunden_insights
kunden_feedbackkunden_feedback
referenz_freigabenreferenz_freigaben
referenz_freigabe_protokollreferenz_freigabe_protokoll
produkt_ereignisseproduct_events
einstieg_ereignisseonboarding_ereignisse
lead_funnel_importelead_funnel_imports
mail_eingaengemail_eingaenge
debitor_nummernkreisdebitor_nummernkreis
ausgehende_email_jobsoutbound_email_jobs
abgelehnte_buchungen_landbilling_country_rejections
dokumentlink_protokolldocument_link_audit
exportauftraegeexport_jobs
newsletter_abonnementnewsletter_subscribers
terminetermine
projekteprojekte
aufmasseaufmasse
aufmass_zeilenaufmass_zeilen
lieferantenlieferanten
katalog_importekatalog_importe
eingangsrechnungeneingangsrechnungen
eingangsrechnung_positioneneingangsrechnung_positionen
bank_kontenbank_konten
bank_umsaetzebank_umsaetze
bank_zuordnungenbank_zuordnungen
anfragenanfragen

Prüfsummen

Datenseiten: Jede Seite trägt im Antwortkopf X-Export-Pruefsumme einen SHA-256 über ihren Rumpf — also über genau die Bytes, die übertragen wurden.

Dateien: Jeder Eintrag der Dateiliste nennt im Feld sha256 die Prüfsumme des Dateiinhalts. Sie entsteht dort, wo die Datei entsteht — im Browser vor dem Hochladen beziehungsweise serverseitig vor dem Speichern. Für Dateien aus der Zeit davor wird sie nachgetragen; solange sie fehlt, steht dort ausdrücklich null und kein Ersatzwert.

Der vom Dateispeicher vergebene ETag wird nicht als Prüfsumme ausgegeben. Er ist bei mehrteiligen Uploads kein Hash des Inhalts, und wer ihn nachrechnen wollte, käme auf ein anderes Ergebnis.

# Seite pruefen
curl -s -D kopf.txt -H "x-export-token: $TOKEN" "$URL&bereich=kunden" > kunden.ndjson
grep -i x-export-pruefsumme kopf.txt
shasum -a 256 kunden.ndjson

# Datei pruefen (sha256 aus der Dateiliste)
shasum -a 256 rechnung-2026-001.pdf

Abbruch und Wiederaufnahme

Jede Datenseite nennt im Kopf X-Export-Naechster-Cursor die Fortsetzungsmarke. Wer abbricht, merkt sich je Bereich die zuletzt gelesene Marke und setzt beim nächsten Lauf mit &cursor=… genau dort wieder an. Die Sortierung ist stabil, ein Nachrücken während des Abrufs verschiebt also keine Zeile.

Die Downloadadressen der Dateien sind wenige Minuten gültig. Laufen sie ab, liefert ein erneuter Aufruf der Dateiliste frische Adressen — solange der Zugang gilt.

Ein vollständiger Export ist EIN Abruf im Sinne der Abrufgrenze, nicht eine Anfrage je Seite: Gezählt wird das Manifest, die Folgeanfragen laufen innerhalb derselben Sitzung. Das Sitzungsfenster beträgt 60 Minuten.

Anfragegrenze

Je Zugang und je Konto gilt ein Token-Bucket: 60 Anfragen als Spitze, danach fünf je Sekunde. Ein vollständiger Export mit rund 70 Anfragen geht damit ohne Wartezeit durch; eine Schleife wird gebremst. Wird die Grenze erreicht, antwortet die Schnittstelle mit 429 und einem Retry-After in Sekunden.

Fehlende Dateien

Kann eine Datei nicht gelesen werden, bricht der Export nicht ab. Sie erscheint im Manifest mit "status": "fehlt" samt Grund. So ist die Lücke sichtbar, statt dass ein scheinbar vollständiges Paket entsteht.

Grenzen

  • Keine feste Zeilengrenze je Bereich; die Daten werden seitenweise vollständig geholt.
  • Das Archiv ist auf 4 GiB begrenzt (ZIP ohne ZIP64). Darüber bricht der Export mit einer klaren Meldung ab.
  • Höchstens fünf offene Exportzugänge je Konto gleichzeitig.
  • Gültigkeit eines Zugangs: 5 Minuten bis 24 Stunden. Abrufe: 1 bis 10.
  • Rate-Limit auf allen Exportendpunkten.
  • Die Antwort wird gestreamt; eine Wiederaufnahme abgebrochener Downloads (Range-Requests) gibt es nicht.

Was nicht enthalten ist

  • Zugriffstokens, Passwort-Hashes und Schlüssel. Sie sind Zugangsmittel, keine Nutzdaten; ein Export würde fremde Zugriffe auf Ihre Dokumente ermöglichen.
  • Interne Support-, Vertriebs- und Bewertungsvermerke der Rolsing & Kokorin GbR (admin_*, leads). Sie sind eigene Aufzeichnungen des Anbieters und nicht vom Kunden bereitgestellt. Auskunft dazu auf Anfrage an datenschutz@kurzhand.de.
  • Protokolle des internen Entwicklungswerkzeugs (agent_logs, agent_messages). Sie haben keinen Bezug zu Ihrem Konto.
  • Rohereignisse des Zahlungsdienstleisters (billing_webhook_events). Ihre Abonnement- und Zahlungsdaten sind unter „abonnements" enthalten; die Originalbelege liegen bei Stripe.
  • Das Cookie-Einwilligungsprotokoll. Es ist an eine pseudonyme Kennung im Browser gebunden und nicht Ihrem Konto zugeordnet.
  • Daten anderer Betriebe. Alle Abfragen sind an Ihr Konto gebunden und zusätzlich durch Row Level Security begrenzt.
  • E-Mail-Anhänge aus dem Mail-Eingang. kurzhand speichert davon nur Metadaten (Dateiname, Typ, Größe); die Datei selbst wird für die Auswertung nur vorübergehend abgerufen und nicht abgelegt.

Fehlerbehandlung

HTTPcodeBedeutung
401nicht angemeldetKein Token übergeben und keine gültige Sitzung.
403unbekanntDas Token gehört zu keinem Zugang. Es wird nicht verraten, ob es je existiert hat.
403widerrufenDer Zugang wurde vom Kontoinhaber widerrufen.
403abgelaufenDie Gültigkeitsdauer ist verstrichen.
403aufgebrauchtDie vereinbarte Zahl der Abrufe ist erreicht.
409zu_viele_offene_exporteEs sind bereits fünf Zugänge offen. Erst einen widerrufen.
429—Rate-Limit. Später erneut versuchen.
500—Serverfehler. Der Auftrag wird als fehlgeschlagen vermerkt und kann erneut abgerufen werden.

Fehlerantworten haben stets die Form {"success": false, "error": "…", "code": "…"}.

Protokollierung

Anlegen, Abruf, Abschluss, Fehlschlag und Widerruf eines Exportzugangs werden protokolliert. Das Protokoll enthält keine Tokens und keine Exportinhalte. Wird ein Konto gelöscht, werden offene Exportzugänge automatisch widerrufen — ein Token soll das Konto nicht überdauern.

Bei technischen Problemen

Schreib uns an support@kurzhand.de. Nenne dabei die Kennung des Exportauftrags (nicht das Token) und den Zeitpunkt des Abrufs. Für Fragen zum Datenschutz: datenschutz@kurzhand.de.