Skip to content

Listen, Filter und Referenzen ​

Diese Regeln gelten für die OAuth-API. Status-Filter, Referenzen, Paginierung und Sortierung betreffen die Listen-Endpoints, Datums- und Zeitparameter gelten endpointübergreifend – sofern der jeweilige Endpoint-Artikel nichts anderes sagt.

Status-Filter ​

Der Parameter status filtert einen Listen-Endpoint nach dem Status seiner Datensätze. Der Standardwert ist all – ohne den Parameter liefert die Liste also alle Werte. Du kannst mehrere Werte kommagetrennt angeben, z.B. status=active,archived. Ein ungültiger Wert liefert 400 validation_error.

EndpointWerteactive bedeutet
Waren abrufenactive, archived, deletedweder archiviert noch gelöscht
Kurse abrufenactive, archivednicht archiviert
Kursteilnehmer abrufenactive, deletednicht storniert
Aufgaben abrufenactive, deletednicht gelöscht
Status abrufenactive, deletednicht gelöscht
Lagerorte abrufen, Lieferanten abrufenactive, deletednicht gelöscht
Mahnstufen abrufenactive, deletednicht gelöscht
Mahnungen abrufenactive, replaced, cancelednicht gelöscht und Mahnstatus active, done oder leer
Kunden abrufenactive, archived, deletedweder archiviert noch gelöscht
Termine abrufen, Mitarbeiter abrufen, Rechnungen abrufen, Honorarnoten abrufenactive, deletednicht gelöscht bzw. nicht storniert
Tags, Referenzen, Warenkategorien, Lagerbewegungen, Bestellungen, Kundengruppen, Auswertungen–kein status-Parameter, siehe unten

Bei den Mahnungen kommt ein zweiter Parameter dazu: dunningStatus (active, replaced, canceled, done) filtert nach dem fachlichen Mahnstatus und wirkt UND-verknüpft mit status. Bei status bedeutet canceled dort: Mahnstatus canceled oder gelöscht – eine gelöschte Mahnung ist immer canceled, unabhängig vom Mahnstatus.

Endpoints ohne Status-Filter ​

Folgende Listen kennen keinen status-Parameter, weil ihre Datensätze keinen Löschzeitpunkt oder Endzustand tragen: Tags, Referenzen, Warenkategorien, Lagerbewegungen, Bestellungen, Kundengruppen und Auswertungen. Sie liefern immer den vollständigen, ungefilterten Bestand.

Einzelabruf ​

Der Einzelabruf eines Datensatzes (GET /{resource}/{uuid}) antwortet immer mit 200 OK, auch für einen Datensatz im Endzustand – gelöscht, storniert, archiviert oder ersetzt. Ein Datensatz, der nicht mehr aktiv ist, liefert also keinen 404.

Ein Einzelabruf antwortet dagegen mit 404, wenn der Datensatz für den Besitzer-Benutzer deines API-Zugangs nicht erreichbar ist: bei Rechnungen, Honorarnoten, Bareinnahmen und Mahnungen, wenn der Mandant des Datensatzes nicht zugänglich ist (siehe Zugriff mit dem Add-on Mandanten). Die Antwort ist in diesem Fall exakt dieselbe wie bei einer unbekannten UUID, damit sie nichts über die Existenz des Datensatzes verrät:

json
{"error":"not_found","error_description":"The requested resource was not found."}

Beim Kursteilnehmer gilt das nicht: Ist der Kurs für den Besitzer-Benutzer nicht lesbar, antwortet der Einzelabruf mit 403 access_denied.

Löschzustand im Response ​

Jeder Datensatz mit einem Löschzustand trägt das Feld deleted (boolean). Hat die zugehörige Entity einen Löschzeitpunkt, kommt zusätzlich deletedAt (ISO 8601, null wenn nicht gelöscht) dazu. Wo ein Datensatz zusätzlich archiviert werden kann, kommt das Feld archived (boolean) dazu. Archiviert und gelöscht sind dabei unabhängige Zustände.

Referenzen auf andere Datensätze ​

Eine Referenz auf einen anderen Datensatz kommt als UUID-String, z.B. "customer": "<uuid>". Eine Liste von Referenzen kommt als Array von UUID-Strings, z.B. "instructors": ["<uuid>"]. Fehlt die Referenz, ist der Wert null (bei Listen []). Es gibt keine eingebetteten Objekte und keine Namen mehr in diesen Feldern.

Um die Daten hinter einer Referenz zu laden, rufst du den zugehörigen Endpoint mit der UUID auf. Dafür brauchst du denselben Scope wie für die Liste, aus der die Referenz stammt.

RelationEndpoint
customerGET /customers/{uuid}
courseGET /courses/{uuid}
invoiceGET /invoices/{uuid}
dunningTypeGET /invoices/dunnings/types/{uuid}
tenancy / tenanciesGET /tenancies/{uuid}
assignedTo, treatingUser, instructors, consumer, createdBy, updatedByGET /users/{uuid}
status (Aufgaben)GET /todos/statuses/{uuid}
tags (Aufgaben)GET /todos/tags/{uuid}
attendees (Kurse)GET /courses/{uuid}/attendees/{attendeeUuid}
category (Kurse, Leistungen)GET /services/categories/{uuid}
category (Waren)GET /wares/categories/{uuid}
ware (Lagerbewegungen, Bestellungen)GET /wares/{uuid}

Referenzen ohne eigenen Endpoint ​

Manche Referenzen haben keinen eigenen Endpoint zum Nachladen. In den Auswertungen betrifft das resource und insuranceService. Ein Zieltyp ohne eigenen OAuth-Endpoint bleibt ebenfalls eine reine UUID ohne Nachlademöglichkeit: MedicalExternalLetter, MotherChildPass, Psychotherapy, Receipt, DVPReportEntry und Transmission. Bei Waren bleibt groups eine Ausnahme von der UUID-Form: Eine Warengruppe hat keine UUID, das Feld liefert weiterhin [{ "name": "..." }].

Ausnahmen im Bestand ​

Eingebettete Objekte in Terminen (customer, service, location, user), Kunden und Rechnungen bleiben vorerst unverändert – sie werden nicht Teil dieser Umstellung. Ausgenommen ist course im Termin: Dort steht nur die UUID des Kurses.

Zugriff mit dem Add-on Mandanten ​

Arbeitet dein Unternehmen mit dem Add-on Mandanten, prüfen Rechnungen, Honorarnoten, Bareinnahmen und Mahnungen den Mandanten jedes Datensatzes. Ein Datensatz ist nur erreichbar, wenn der Besitzer-Benutzer deines API-Zugangs Zugriff auf seinen Mandanten hat und dort das passende Recht besitzt:

EndpointRecht je Mandant
GET /invoices, GET /fee-notes, GET /cash-revenues und die jeweiligen EinzelabrufeRechnungen: Anzeigen
GET /invoices/dunnings und EinzelabrufMahnwesen: Anzeigen
POST /invoices/dunningsMahnwesen: Erstellen

Daraus folgt:

  • Eine Liste enthält nur Datensätze aus Mandanten, die der Besitzer-Benutzer sehen darf. meta.total zählt nur diese Datensätze.

  • Datensätze ohne Mandant sind nicht erreichbar.

  • Der Einzelabruf eines Datensatzes außerhalb dieser Mandanten antwortet mit 404 not_found, exakt wie bei einer unbekannten UUID.

  • POST /invoices/dunnings für eine Rechnung eines nicht zugänglichen Mandanten, oder ohne das Recht Mahnwesen: Erstellen in diesem Mandanten, antwortet mit 404 not_found (error_description: Invoice not found.) – wie bei einer unbekannten Rechnung.

  • GET /fee-note-services/turnover und GET /fee-note-services/productivity antworten mit 403 access_denied, wenn dem Besitzer-Benutzer kein Mandant zugänglich ist. Eine Auswertung nur über Datensätze ohne Mandant würde sonst vollständig wirken.

Ohne das Add-on Mandanten ändert sich nichts.

Schlanke Antworten mit view ​

Sieben Listen-Endpoints nehmen den Query-Parameter view an: GET /appointments, GET /customers, GET /executions, GET /invoices, GET /locations, GET /services und GET /users. Erlaubt sind default (Standard) und slim. Mit slim liefert die Liste pro Datensatz nur die wichtigsten Felder. Verweise auf andere Datensätze kommen dabei als { "id": "<uuid>" }.

Ein anderer Wert oder ein Array ergibt 400 validation_error mit Parameter "view" must be "default" or "slim". Einzelabrufe lesen den Parameter nicht. Wird view mehrfach angegeben, gilt die letzte Angabe – ?view=default&view=slim liefert also die schlanke Antwort.

EndpointFelder mit view=slim
GET /appointmentsid, title, start, end, allDay, status, type, source, deleted, deletedAt, createdAt, updatedAt. customer, service, location und user als { "id" }, course als Objekt mit id, name, maxAttendees und currentAttendees
GET /customersid, title, firstName, lastName, companyName, sex, birthday, email, phone, street, zip, city, country, customerNumber, archived, deleted, createdAt, updatedAt, deletedAt
GET /executionsid, user, location, service (jeweils als { "id" })
GET /invoicesid, sequence, formattedSequence, status, net, subTotal, gross, discount, outstandingBalance, invoicingDate, paidAt, isFullyPaid, deleted, createdAt, updatedAt. customer als { "id" }
GET /locationsid, name, abbreviation, street, zip, city, country, bookable, sort
GET /servicesid, name, duration, price, type, sort. category als { "id" }
GET /usersid, title, firstName, lastName, abbreviation, isDoctor, deleted, createdAt, updatedAt

Paginierung ​

Eine paginierte Liste verwendet die Parameter offset (Standard: 0) und limit (Standard: 50, maximal 200) und liefert die Gesamtanzahl in meta.total. Zwei Endpoints liefern ihre Ergebnisse ohne Paginierung: GET /todos/{uuid}/references liefert alle Referenzen einer Aufgabe ohne Datenbank-Paginierung, und GET /customers/{uuid}/groups liefert alle Kundengruppen eines Kunden ohne offset/limit.

offset, limit und maxDays (bei Nächste freie Slots) müssen ganze Zahlen sein. Ein anderer Wert (abc, 5.5, 1e3, Leerzeichen) ergibt 400 validation_error mit Parameter "limit" must be an integer. (bzw. dem jeweiligen Parameternamen). Früher wurde so ein Wert stillschweigend in eine Zahl umgewandelt. Ein Wert über oder unter der Grenze wird weiterhin auf die Grenze gesetzt, ein fehlender Parameter verwendet den Standardwert.

meta.total zählt in der Datenbank, bevor die Berechtigungsprüfung greift. Die Mandanten-Prüfung bei Rechnungen, Honorarnoten, Bareinnahmen und Mahnungen ist davon ausgenommen: Sie wirkt schon in der Abfrage, siehe Zugriff mit dem Add-on Mandanten. Was das für die Paginierung bedeutet, steht unter Inkrementeller Abgleich.

Standard-Sortierung ​

Ohne einen Sortier- oder Zeitstempel-Filter sortiert eine Liste nach interner ID aufsteigend. Die interne ID ist dabei die Datenbank-ID des Datensatzes, nicht das Response-Feld id (das ist die UUID). Diese Reihenfolge ist stabil über die gesamte Paginierung. Das gilt für alle umgestellten Listen, ausdrücklich auch für Mahnstufen, Kundengruppen, Aufgaben, Kurse, Kursteilnehmer, Termine, Kunden, Mitarbeiter, Honorarnoten und Rechnungen.

Drei Ausnahmen bleiben bestehen:

  • GET /wares sortiert bei gesetztem Parameter term nach Relevanz statt nach interner ID.
  • Lagerbewegungen und Bestellungen behalten im Filter-Modus ihre bisher dokumentierte Sortierung.
  • GET /fee-note-services sortiert nach date absteigend, bei gleichem Datum nach id absteigend.

Datums- und Zeitparameter ​

Die OAuth-API parst die Datums- und Zeit-Parameter der Query strikt, ebenso start beim Termin buchen. Das betrifft u.a. from/until bei den Auswertungen, dueDateFrom/dueDateTo bei Aufgaben, startDate/endDate bei freien Slots, from/until bei Terminen und start beim Termin buchen.

Angenommen werden:

  • ein reines Datum YYYY-MM-DD (z.B. 2026-08-24) – nur an Endpoints, die keine Uhrzeit verlangen
  • Datum und Uhrzeit mit Minutenpräzision, ohne Sekunden (2026-08-24T10:00) – die Sekunden werden dabei als 00 angenommen
  • Datum und Uhrzeit mit Sekunden (2026-08-24T10:00:00) oder mit Millisekunden (2026-08-24T08:00:00.000Z)
  • jeweils mit Zeitzonen-Offset (2026-08-24T10:00+02:00, 2026-08-24T10:00:00+02:00) oder als UTC mit Z (2026-08-24T10:00Z, 2026-08-24T08:00:00.000Z)

Alle Felder sind zweistellig (2026-08-04, nicht 2026-8-4), das Jahr liegt zwischen 1000 und 9999. Ein Offset hat die Form Z, ±HH:MM oder ±HHMM und liegt höchstens bei ±14:00. Die Kleinbuchstaben t und z sind erlaubt. Das gilt für die Query-Parameter und für start.

Mit 400 validation_error abgelehnt werden Zonennamen (Europe/Vienna, CEST, UTC), ein Offset ohne Minuten (+02), ein Offset mit Sekunden, einstellige Monats-, Tag- oder Stundenangaben und ein Leerzeichen vor dem Offset.

Alle Body-Felder, die nur ein Datum erwarten (z.B. date, birthday, customDate, customStartDate), nehmen ausschließlich YYYY-MM-DD als echtes Kalenderdatum an, mit einem Jahr zwischen 1000 und 9999. Ein Zeitstempel, einstellige Angaben oder ein unmögliches Datum ergeben 400 validation_error mit Invalid <Feld>. Expected a date (YYYY-MM-DD).; null oder ein fehlendes Feld bleibt unverändert. Body-Felder mit Zeitangabe (start beim Termin buchen, dueDate bei Aufgabe erstellen und Aufgabe bearbeiten) akzeptieren die oben genannten ISO-Formate.

Ein Zeitzonen-Offset im Wert wird intern auf die Server-Zeitzone normalisiert. Ein unmögliches Kalenderdatum wie 2026-02-31 ergibt 400 validation_error, keine stille Umrechnung auf einen Nachbartag. Eine Zeitangabe mit Leerzeichen statt T (2026-08-24 10:00) oder ein relatives Format (tomorrow) werden ebenfalls abgelehnt.

Ist der Parameter gesetzt, aber ungültig, antwortet der Endpoint mit dem Format-Fehler des jeweiligen Parameters – nicht mit einer Meldung, dass der Parameter fehlt oder required ist, zum Beispiel:

Parameter "from" must be an ISO 8601 date or date/time (e.g. "2026-08-24", "2026-08-24T10:00", "2026-08-24T10:00:00", "2026-08-24T10:00:00+02:00" or "2026-08-24T08:00:00.000Z").

Ein + im Zeitzonen-Offset muss in der URL als %2B prozent-kodiert werden.

Bei einem Zeitraum aus zwei Parametern darf der Beginn nicht nach dem Ende liegen: createdSince nach createdUntil, updatedSince nach updatedUntil und dueDateFrom nach dueDateTo ergeben 400 validation_error. Bei Terminen gilt dasselbe für from und until.

Zusammenspiel mit Zeitstempel-Filtern ​

Der Parameter deletedSince liefert nur gelöschte bzw. stornierte Datensätze und wirkt UND-verknüpft mit status. Kombinierst du deletedSince mit status=active, widersprechen sich beide Bedingungen – die Antwort ist eine leere Liste, kein Fehler. Mit status=deleted (oder ohne status) liefert deletedSince wie erwartet die gelöschten Datensätze ab dem angegebenen Zeitpunkt.

deletedSince lässt sich an vielen Endpoints außerdem nicht mit createdSince, createdUntil, updatedSince oder updatedUntil kombinieren – das ist eine eigene Regel und unabhängig von status. Details dazu, zum Cursor über mehrere Seiten und zum Restrisiko der Paginierung stehen unter Inkrementeller Abgleich.

Offisy GmbH