Skip to content

Fehler, Statuscodes und Limits ​

Gilt ab Work7 0.10.0 · Stand 05.11.2026

Fehlerformat ​

Alle Fehler der Public API haben dieselbe Form:

json
{
  "error": "insufficient_scope",
  "message": "Scope site-reports:read fehlt.",
  "details": { "required": "site-reports:read", "scopes": ["core:read"] }
}
HTTPerrorBedeutungWas tun
400validation_errorPflichtfeld fehlt oder Wert ungültigmessage lesen, Eingabe korrigieren
401unauthorizedSchlüssel fehlt, ungültig oder widerrufenHeader prüfen, ggf. neuen Schlüssel anlegen
403insufficient_scopeSchlüssel hat den Scope nichtScope beim Anlegen des Schlüssels ergänzen
403module_disabledBereich für den Betrieb nicht freigeschaltetBetrieb muss das Modul buchen; Daten bleiben erhalten
403FORBIDDEN mit code demo_lockedDer Betrieb ist ein Demo-Account; dort sind Anbindungen gesperrt (seit API 1.23)Den Schlüssel in einem echten Betrieb anlegen
404NOT_FOUNDDatensatz existiert nicht oder gehört einem anderen BetriebID prüfen
409CONFLICTZustandskonflikt, zum Beispiel Statuswechsel nicht erlaubtdetails.code auswerten (siehe unten)
413payload_too_largeBody zu groß: JSON über 64 KB bzw. Upload über der GrenzeDaten aufteilen
415unsupported_media_typeSchreibender Aufruf mit falschem oder fehlendem Content-Type (kaputtes JSON: 400)Content-Type: application/json senden
429rate_limitedZu viele AnfragenRetry-After (Sekunden) abwarten
500INTERNAL_ERRORUnerwarteter Fehlererneut versuchen, bei Wiederholung melden

Fehler aus der Geschäftslogik (Validierung, Konflikte, nicht gefunden) tragen zusätzlich code und timestamp. Validierungsfehler kommen seit API 1.21 immer als validation_error, auch wenn sie aus der Geschäftslogik stammen (vorher dort BAD_REQUEST).

Ein Demo-Account (seit API 1.23) antwortet auf jeden Aufruf so:

json
{
  "error": "FORBIDDEN",
  "message": "Im Demo-Account sind Anbindungen gesperrt.",
  "code": "demo_locked",
  "timestamp": "2026-11-05T09:00:00.000Z",
  "details": { "code": "demo_locked", "feature": "public_api" }
}

Validierungsfehler je Feld ​

Bei schreibenden Aufrufen nennt details das betroffene Feld:

json
{
  "error": "validation_error",
  "message": "is_active: Unbekanntes Feld.",
  "details": {
    "field": "is_active",
    "errors": [{ "field": "is_active", "message": "Unbekanntes Feld." }]
  }
}

details.field ist das erste Feld, details.errors listet bis zu 20 Fehler. Unbekannte Felder, kaputtes JSON und ein leerer Body ergeben ebenfalls 400 validation_error.

Konflikte (details.code) ​

details.codeBedeutung
idempotency_conflictDer Idempotency-Key wurde schon für eine andere Anfrage (anderer Body, Pfad oder Query-String) verwendet
idempotency_in_progressDie erste Anfrage mit diesem Idempotency-Key läuft noch; später erneut senden
idempotency_outcome_unknownDie erste Anfrage mit diesem Idempotency-Key ist seit mehr als 5 Minuten offen, ihr Ausgang ist unbekannt; vor einem neuen Schlüssel per GET prüfen, ob der Datensatz angelegt wurde
has_documentsDer Kontakt hat versendete Belege und kann nicht gelöscht werden; stattdessen archivieren
has_open_itemsArchivieren oder Löschen des Kontakts würde andere Daten mitändern oder löschen; details.items zählt sie je Art (siehe unten). In der App bereinigen, dann erneut senden
duplicate_project_numberDie Projektnummer ist im Betrieb schon vergeben (Projektnummern sind je Betrieb eindeutig, seit API 1.22); andere Nummer wählen oder ohne project_number anlegen
NUMBER_TAKENNur POST /projects ohne project_number und POST /contacts: Die von Work7 vergebene Projekt- bzw. Kundennummer wurde im selben Moment im Betrieb anders vergeben (gleichzeitige Anlage, von Hand gesetzte Nummer). details.number nennt die Nummer, details.document_type project oder customer. Nichts wurde angelegt; ein erneuter Versuch kann klappen, Work7 überspringt die Nummer dann (seit API 1.22). Auch mit demselben Idempotency-Key, die Antwort wird nicht gespeichert
PROJECT_STATUS_STALEDer Projektstatus hat sich seit expected_status_id geändert; details.current_status_id nennt den aktuellen. Gilt auch bei reinen Feldänderungen. Projekt neu lesen und erneut entscheiden

Kontakt archivieren oder löschen: has_open_items ​

In der App nimmt das Archivieren oder Löschen eines Kontakts dessen offene Vorgänge mit. Über die API ändern PATCH /contacts/{id} mit status: archived und DELETE /contacts/{id} nur den Kontakt selbst. Gibt es solche Vorgänge, antwortet die API mit 409 CONFLICT, details.code = "has_open_items", und ändert nichts:

json
{
  "error": "CONFLICT",
  "code": "has_open_items",
  "message": "Der Kontakt hat offene Vorgänge, die beim Archivieren mitgeändert oder gelöscht würden (1 Angebotsentwürfe, 2 Anfragen). Bitte zuerst in der App bereinigen.",
  "details": { "code": "has_open_items", "items": { "offer_drafts": 1, "inquiries": 2 } }
}
Schlüssel in details.itemsArchivierenLöschen
offer_draftsAngebotsentwürfeAngebotsentwürfe
invoice_draftsRechnungsentwürfeRechnungsentwürfe
sales_document_drafts–Entwürfe von Lieferscheinen und Kostenvoranschlägen
open_rapportsnicht abgeschlossene Rapportenicht abgeschlossene Rapporte
open_acceptance_protocolsnicht abgeschlossene Abnahmeprotokollenicht abgeschlossene Abnahmeprotokolle
inquiriesoffene Anfragen (nicht gewonnen, verloren oder abgelehnt)alle Anfragen
projectslaufende Projekte (nicht abgeschlossen oder archiviert)alle Projekte

details.items nennt nur Arten mit mindestens einem Eintrag.

Schreibende Aufrufe ​

  • Body nur als JSON mit Content-Type: application/json (Zusatz charset=utf-8 erlaubt). Anderer oder fehlender Content-Type: 415 unsupported_media_type.
  • Höchstens 64 KB je Body, gezählt beim Empfang (unabhängig vom Header Content-Length). Darüber: 413 payload_too_large mit details.maxBytes.
  • Ausnahme POST /inquiries: Der Body wird auch ohne Content-Type: application/json als JSON gelesen (für Website-Formulare), Größengrenze und Feldprüfung gelten trotzdem; unbekannte Felder werden dort ignoriert.

Idempotency-Key ​

Schreibende POST-Aufrufe nehmen den optionalen Header Idempotency-Key an, damit ein Netzwerkfehler beim Wiederholen keinen zweiten Datensatz erzeugt:

http
POST /api/v1/<bereich>
Content-Type: application/json
Idempotency-Key: 6f1c2a9e-3b7d-4c55-9a43-0d2e8f61b7aa
  • Format: 16 bis 128 druckbare ASCII-Zeichen ohne Leerzeichen, zum Beispiel eine UUID. Anderes Format: 400 validation_error mit details.field = "Idempotency-Key".
  • Gleicher Schlüssel mit gleichem Body und Pfad: Die API schreibt nicht erneut, sondern liefert die gespeicherte Antwort (Status, Body und Location) mit dem Header Idempotent-Replay: true.
  • Gleicher Schlüssel mit anderem Body, Pfad oder Query-String: 409 CONFLICT, details.code = "idempotency_conflict".
  • Läuft die erste Anfrage noch: 409 CONFLICT, details.code = "idempotency_in_progress".
  • Ist die erste Anfrage seit mehr als 5 Minuten offen (zum Beispiel nach einem Absturz), ist ihr Ausgang unbekannt: 409 CONFLICT, details.code = "idempotency_outcome_unknown". Die API führt sie nicht erneut aus. Vor einem neuen Schlüssel per GET prüfen, ob der Datensatz schon angelegt wurde.
  • Gespeichert werden Antworten mit 2xx und 4xx. Nach einem 5xx oder einem 409 mit details.code = "NUMBER_TAKEN" darf derselbe Schlüssel erneut gesendet werden.
  • Ein Schlüssel gilt je API-Schlüssel und 24 Stunden; danach wird er wie ein neuer behandelt. Gespeicherte Antworten werden 24 Stunden aufbewahrt und danach gelöscht: beim nächsten Aufruf des Betriebs mit Idempotency-Key, sonst beim täglichen Aufräumen (spätestens rund einen Tag nach Ablauf).
  • Wird ein Kontakt über die API archiviert oder gelöscht, löscht Work7 die gespeicherten Antworten, die auf ihn zeigen (Location: /api/v1/contacts/{id}). Eine spätere Wiederholung mit einem solchen Schlüssel läuft wie ein neuer Aufruf.
  • Ohne Header verhält sich jeder Aufruf wie bisher.

Welche Endpunkte den Header auswerten, steht in der API-Referenz beim jeweiligen Endpunkt.

Rate-Limit ​

600 Anfragen pro Minute je Schlüssel, Lesen und Schreiben zusammen. Davon dürfen höchstens 60 pro Minute schreibend sein (POST, PATCH, DELETE); Lesen, mit 403 abgewiesene Aufrufe und Uploads der Datenablage (POST /inbound, dort gilt nur das Gesamtlimit) zählen dort nicht mit. Bei Überschreitung eines der beiden Limits antwortet die API mit 429 rate_limited und dem Header Retry-After (Sekunden); beim Schreib-Limit steht in details zusätzlich scope: "write" und limit: 60. Für Massenabgleiche: Listen mit limit=200 holen, statt Einzelobjekte in Schleifen zu laden.

Pagination ​

Listen liefern data und pagination:

json
{
  "data": [ { "id": 1 } ],
  "pagination": { "limit": 50, "offset": 0, "total": 137, "hasMore": true }
}
  • limit maximal 200, Standard 50.
  • Nächste Seite: offset = offset + limit, solange hasMore wahr ist.
  • /time-entries verwendet page und pageSize (maximal 100) und liefert pageInfo im data-Objekt.

Datumsformate ​

  • Zeitstempel: ISO 8601 mit Zeitzone, zum Beispiel 2026-09-12T10:00:00+02:00.
  • Tagesdaten (from, to, Termine): YYYY-MM-DD, interpretiert in Europe/Vienna.

Versionierung ​

Der Pfad trägt die Hauptversion (/api/v1). Innerhalb von v1 kommen Felder und Endpunkte hinzu, bestehende Felder werden nicht entfernt oder umbenannt. Was in welcher API-Version hinzugekommen ist, steht unter API-Änderungen; dort liegen auch ältere Fassungen der Spezifikation zum Herunterladen.

Einzelne Pfadsegmente werden ins Englische umbenannt (/anfragen → /inquiries, /kontakte → /contacts, /rapports → /site-reports); die alten Segmente bleiben als Aliase dauerhaft erreichbar und liefern identische Antworten — kein Redirect, keine Frist zum Umstellen.

Support ​

Fragen zur Integration: office@work7.net. Bitte Betrieb, Schlüssel-Prefix (w7_live_<prefix>_…) und den Wert des Antwort-Headers x-request-id mitschicken, nie den vollständigen Schlüssel.

Work7 · Software für Handwerksbetriebe · Doku-Stand Version 0.9.0 (Build f72f8bdd)