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": "https://kurzhand.de/api/export/paket?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
| Endpunkt | Authentifizierung | Wirkung |
|---|---|---|
POST /api/export/jobs | Sitzung des Kontoinhabers | Legt 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/jobs | Sitzung des Kontoinhabers | Listet die eigenen Exportzugänge mit Status, Ablauf, Abrufzahl, Größe und Prüfsumme. Ohne Tokens. |
POST /api/export/jobs/widerruf | Sitzung des Kontoinhabers | Widerruft einen Zugang (Body: {"id": "…"}) oder ohne Body alle offenen Zugänge. Wirkt sofort. |
GET <supabase>/functions/v1/export-v2?aktion=manifest | Export-Token oder Sitzung | Verzeichnis 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 Sitzung | Eine Seite eines Datenbereichs als NDJSON. Antwortköpfe nennen Zeilenzahl, SHA-256 der Seite und die Fortsetzungsmarke. |
GET …?aktion=dateien&bucket=…&cursor=… | Export-Token oder Sitzung | Dateiliste 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.json | Quelle |
|---|---|
profil | profiles |
kunden | kunden |
angebote | angebote |
rechnungen | rechnungen |
rechnungs_zahlungen | rechnungs_zahlungen |
leistungen | leistungen |
materialien | materialien |
vorlagen | vorlagen |
dokument_vorlagen | dokument_vorlagen |
auftraege | auftraege |
activity | activity |
erinnerungen | reminders |
agent_tasks | agent_tasks |
abonnements | subscriptions |
angebot_signaturen | angebot_signaturen |
rechnungsnummern_nachweis | invoice_numbers |
korrekturbelege | korrekturbelege |
rechnung_abschlaege | rechnung_abschlaege |
serienrechnungen | recurring_invoices |
auftrag_serien | auftrag_serien |
auftrag_fotos | auftrag_fotos |
abnahmen | abnahmen |
abnahme_maengel | abnahme_maengel |
abnahme_fotos | abnahme_fotos |
abnahme_signaturen | abnahme_signaturen |
zeiteintraege | zeiteintraege |
zeiteintrag_aenderungen | zeiteintrag_aenderungen |
ki_guthaben | ai_credits |
ki_nutzung | ai_usage |
ki_bestaetigungen | ai_confirm_tokens_used |
ki_provenienz | ki_provenienz |
team_mitglieder | team_mitglieder |
team_mitglied_episoden | team_mitglied_episoden |
team_audit_log | team_audit_log |
team_einladungen | team_einladungen |
urlaubsanspruch | urlaubsanspruch |
abwesenheiten | abwesenheiten |
zeit_korrektur_antraege | zeit_korrektur_antraege |
auftrag_berichte | auftrag_berichte |
dokument_freischaltungen | document_unlocks |
rechtstext_einbeziehungen | legal_acceptance_log |
unternehmerbestaetigungen | b2b_bestaetigungen |
support_tickets | support_tickets |
support_ticket_nachrichten | support_ticket_messages |
produkt_ereignisse | product_events |
lead_funnel_importe | lead_funnel_imports |
mail_eingaenge | mail_eingaenge |
debitor_nummernkreis | debitor_nummernkreis |
ausgehende_email_jobs | outbound_email_jobs |
abgelehnte_buchungen_land | billing_country_rejections |
dokumentlink_protokoll | document_link_audit |
exportauftraege | export_jobs |
newsletter_abonnement | newsletter_subscribers |
termine | termine |
projekte | projekte |
aufmasse | aufmasse |
aufmass_zeilen | aufmass_zeilen |
lieferanten | lieferanten |
katalog_importe | katalog_importe |
eingangsrechnungen | eingangsrechnungen |
eingangsrechnung_positionen | eingangsrechnung_positionen |
bank_konten | bank_konten |
bank_umsaetze | bank_umsaetze |
bank_zuordnungen | bank_zuordnungen |
anfragen | anfragen |
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.pdfAbbruch 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
| HTTP | code | Bedeutung |
|---|---|---|
| 401 | nicht angemeldet | Kein Token übergeben und keine gültige Sitzung. |
| 403 | unbekannt | Das Token gehört zu keinem Zugang. Es wird nicht verraten, ob es je existiert hat. |
| 403 | widerrufen | Der Zugang wurde vom Kontoinhaber widerrufen. |
| 403 | abgelaufen | Die Gültigkeitsdauer ist verstrichen. |
| 403 | aufgebraucht | Die vereinbarte Zahl der Abrufe ist erreicht. |
| 409 | zu_viele_offene_exporte | Es 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 hallo@kurzhand.de. Nenne dabei die Kennung des Exportauftrags (nicht das Token) und den Zeitpunkt des Abrufs. Für Fragen zum Datenschutz: datenschutz@kurzhand.de.
