Termine abrufen
Gibt eine paginierte Liste aller Termine des Unternehmens zurück.
Request
GET /api/oauth/v2/resource/appointments
Header
| Header | Wert |
|---|---|
Authorization | Bearer {access_token} |
Query-Parameter
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---|---|
offset | integer | Nein | Startposition (Standard: 0) |
limit | integer | Nein | Anzahl Ergebnisse (Standard: 50, Max: 200) |
from | string (ISO 8601) | Bedingt | Zeitraum-Start – als Datum (2026-03-01) oder ISO 8601 (2026-03-01T00:00:00+01:00). Pflicht, solange weder updatedSince noch deletedSince gesetzt ist – siehe Hinweis unter der Tabelle. |
until | string (ISO 8601) | Bedingt | Zeitraum-Ende – als Datum (2026-03-31) oder ISO 8601 (2026-03-31T23:59:59+01:00). Pflicht, solange weder updatedSince noch deletedSince gesetzt ist – siehe Hinweis unter der Tabelle. |
type | string | Nein | Kommaseparierte Liste von Termintypen (Standard: admin,customer,course). Erlaubte Werte: admin, customer, course, pause, private, vacation, illness, official_holiday, external. Sonderwert all gibt alle Typen zurück. |
customerId | string (UUID) | Nein | Filtert Termine auf einen bestimmten Kunden. Gibt nur Termine zurück, bei denen der Kunde direkt zugewiesen ist oder in der Attendee-Liste eines Kurs-Termins vorkommt. |
view | string | Nein | Antwortform: default (Vorgabe) oder slim |
updatedSince | string (ISO 8601) | Nein | Nur Termine, die ab diesem Zeitpunkt geändert wurden (updatedAt >=). Neu angelegte Termine kommen mit, weil der Änderungszeitpunkt beim Anlegen gesetzt wird. |
updatedUntil | string (ISO 8601) | Nein | Nur Termine, die bis zu diesem Zeitpunkt geändert wurden (updatedAt <=) |
deletedSince | string (ISO 8601) | Nein | Nur stornierte Termine mit Löschzeitpunkt ab diesem Wert (deletedAt >=). Aktive Termine liefert dieser Filter nicht. |
Hinweis: Du fragst Termine auf zwei Wegen ab. Mit
fromunduntilholst du ein Terminfenster – die API gibt nur Termine in diesem Zeitraum zurück. MitupdatedSince(oderdeletedSince) holst du stattdessen Änderungen unabhängig vom Termindatum.Eines von beiden brauchst du immer. Fehlt
fromoderuntil, und ist wederupdatedSincenochdeletedSincegesetzt, antwortet die API mit400 validation_error.
Wichtig: Schicke beim inkrementellen Abgleich kein Terminfenster mit.
from/untilundupdatedSincewirken sonst zusammen – du bekommst nur Änderungen innerhalb dieses Zeitfensters und verpasst den Rest. Für einen vollständigen Abgleich lässt dufromunduntilweg.
Tipp: Bei Angabe ohne Uhrzeit (z.B.
2026-03-01) wirdfromauf00:00:00unduntilauf23:59:59gesetzt – ideal für tagesgenaue Abfragen.
Hinweis: Dieser Endpoint erfordert immer die Scopes
appointmentsundusers. Für Kurs-Termine (Typcourseoderall) ist zusätzlich der Scopecustomerserforderlich.
Hinweis: Wird
customerIdangegeben, ist zusätzlich der Scopecustomerserforderlich – unabhängig vom gewählten Termintyp.
Hinweis zu Kurs-Terminen: Kurs-Termine (
type=course) enthalten personenbezogene Kundendaten (attendees.customer) und Mitarbeiterdaten (instructors). Dafür ist der Scopecustomerserforderlich. Fragst du explizit?type=course(oder?type=all) an, ohne diesen Scope zu besitzen, antwortet die API mit403 insufficient_scope.
Hinweis: Für einen Abgleich genügt
updatedSince– der Wert deckt neue und geänderte Termine ab. Stornierte Termine holst du in einem zweiten Request mitdeletedSince.
Hinweis:
deletedSincelässt sich nicht mitupdatedSinceoderupdatedUntilkombinieren. Die API antwortet dann mit400 validation_error, weildeletedSincedie Ergebnismenge auf stornierte Termine umschaltet. Frage Änderungen und Stornierungen deshalb in zwei getrennten Requests ab.
Hinweis: Formate, Zeitzonen-Verhalten, Sortierung im Filter-Modus, Cursor-Strategie und die
meta.total-Falle gelten für alle Zeitstempel-Filter gleich – siehe Inkrementeller Abgleich.Beim Lösch-Abgleich mit
deletedSincekommt ein weiterer Fall des Restrisikos hinzu. Ein während des Durchlaufs wiederhergestellter Termin behält seinen alten Zeitstempel und rutscht unter den neuen Cursor.
Hinweis zu wiederhergestellten Terminen: Ein stornierter Termin kann wiederhergestellt werden. Dabei fällt der Löschzeitpunkt weg. Der Termin verschwindet damit aus dem Lösch-Abgleich und erscheint stattdessen im Änderungs-Abgleich mit
deleted=false.Hast du den Termin lokal auf die Storno-Meldung hin entfernt, holst du ihn dir so zurück. Voraussetzung: dein Client wertet
deletedaus. Fahre deshalb immer beide Abgleiche, nicht nur den Lösch-Abgleich.
Hinweis zu Stornierungen aus dem Altbestand:
deletedSincefindet nur Termine mit gesetztem Löschzeitpunkt. Termine, die davor storniert wurden, erreichst du damit nicht – auch nicht mit einem sehr frühen Datum.
Hinweis: Termine unterstützen kein
createdSinceund keincreatedUntil. Das FeldcreatedAtliefert am Termin den Buchungszeitpunkt, nicht den Erstellungszeitpunkt des Datensatzes. Für den inkrementellen Abgleich über Termine verwendest du deshalbupdatedSince.
Response
Erfolg (200 OK)
Beispiel: Einzeltermin (type: "customer")
{
"data": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Erstgespräch",
"start": "2026-03-15T10:00:00+01:00",
"end": "2026-03-15T11:00:00+01:00",
"allDay": false,
"status": "active",
"type": "customer",
"course": null,
"note": "Bitte nüchtern erscheinen",
"customer": {
"id": "f0e1d2c3-b4a5-6789-fedc-ba9876543210",
"firstName": "Anna",
"lastName": "Muster"
},
"service": {
"id": "c3d4e5f6-a7b8-9012-cdef-012345678901",
"name": "Erstgespräch"
},
"location": {
"id": "e5f6a7b8-c9d0-1234-efab-234567890123",
"name": "Studio 1",
"street": "Hauptstraße 1",
"zip": "1010",
"city": "Wien",
"country": "AT"
},
"user": {
"id": "d4e5f6a7-b8c9-0123-defa-123456789012",
"firstName": "Max",
"lastName": "Muster",
"abbreviation": "MM"
},
"source": "oauth",
"deleted": false,
"deletedAt": null,
"stornoReason": null,
"createdAt": "2026-03-01T09:00:00+01:00",
"updatedAt": "2026-03-01T09:00:00+01:00"
}
],
"meta": {
"total": 500,
"offset": 0,
"limit": 50
}
}Beispiel: Kurs-Termin (type: "course")
{
"data": [
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"title": "Yoga Anfänger",
"start": "2026-03-20T09:00:00+01:00",
"end": "2026-03-20T10:00:00+01:00",
"allDay": false,
"status": "active",
"type": "course",
"course": {
"id": "c3d4e5f6-a7b8-9012-cdef-012345678901",
"name": "Yoga Anfänger",
"maxAttendees": 20,
"currentAttendees": 12,
"color": "#ff0000",
"instructors": [
{ "id": "d4e5f6a7-b8c9-0123-defa-123456789012", "firstName": "Max", "lastName": "Muster" }
],
"location": { "id": "e5f6a7b8-c9d0-1234-efab-234567890123", "name": "Studio 1" },
"attendees": [
{
"customer": { "id": "f0e1d2c3-b4a5-6789-fedc-ba9876543210", "firstName": "Anna", "lastName": "Muster" },
"bookingDate": "2026-03-01T10:00:00+01:00"
}
]
},
"note": null,
"customer": null,
"service": null,
"location": {
"id": "e5f6a7b8-c9d0-1234-efab-234567890123",
"name": "Studio 1",
"street": "Hauptstraße 1",
"zip": "1010",
"city": "Wien",
"country": "AT"
},
"user": {
"id": "d4e5f6a7-b8c9-0123-defa-123456789012",
"firstName": "Max",
"lastName": "Muster",
"abbreviation": "MM"
},
"source": "oauth",
"createdAt": "2026-03-01T09:00:00+01:00",
"updatedAt": "2026-03-01T09:00:00+01:00"
}
],
"meta": {
"total": 500,
"offset": 0,
"limit": 50
}
}Response-Felder
| Feld | Typ | Beschreibung |
|---|---|---|
data | Array | Liste der Termine |
data[].id | string (UUID) | Eindeutige Termin-ID |
data[].title | string | Titel des Termins |
data[].start | string (ISO 8601) | null | Startzeit |
data[].end | string (ISO 8601) | null | Endzeit |
data[].allDay | boolean | Ganztägiger Termin |
data[].status | string | Status des Termins (active oder request) |
data[].type | string | Termintyp (admin, customer, course, pause, private, vacation, illness, official_holiday, external) |
data[].course | object | null | Kurs-Daten – nur bei type: "course" befüllt, sonst null |
data[].course.id | string (UUID) | Kurs-ID |
data[].course.name | string | Kursname |
data[].course.maxAttendees | integer | null | Maximale Teilnehmeranzahl |
data[].course.currentAttendees | integer | Aktuelle Teilnehmeranzahl |
data[].course.color | string | null | Farbe als Hex-Code (z.B. #ff0000) |
data[].course.instructors | Array | Liste der Kursleiter |
data[].course.instructors[].id | string (UUID) | Kursleiter-ID |
data[].course.instructors[].firstName | string | Vorname des Kursleiters |
data[].course.instructors[].lastName | string | Nachname des Kursleiters |
data[].course.location | object | null | Kursort |
data[].course.location.id | string (UUID) | Ort-ID |
data[].course.location.name | string | Ortsname |
data[].course.attendees | Array | Liste der Kursteilnehmer |
data[].course.attendees[].customer | object | Teilnehmer-Kurzinfo |
data[].course.attendees[].customer.id | string (UUID) | Kunden-ID |
data[].course.attendees[].customer.firstName | string | Vorname |
data[].course.attendees[].customer.lastName | string | Nachname |
data[].course.attendees[].bookingDate | string (ISO 8601) | null | Buchungsdatum |
data[].note | string | Interne Notiz |
data[].customer | object | null | Verknüpfter Kunde |
data[].customer.id | string (UUID) | Kunden-ID |
data[].customer.firstName | string | Vorname |
data[].customer.lastName | string | Nachname |
data[].service | object | null | Verknüpfte Leistung |
data[].service.id | string (UUID) | Leistungs-ID |
data[].service.name | string | Name der Leistung |
data[].location | object | null | Ausführungsort des Termins |
data[].location.id | string (UUID) | Standort-ID |
data[].location.name | string | Standortname |
data[].location.street | string | null | Straße |
data[].location.zip | string | null | PLZ |
data[].location.city | string | null | Ort |
data[].location.country | string | null | Land |
data[].user | object | null | Dem Termin zugeordnete:r Mitarbeiter:in |
data[].user.id | string (UUID) | Mitarbeiter-ID |
data[].user.firstName | string | Vorname |
data[].user.lastName | string | Nachname |
data[].user.abbreviation | string | null | Kürzel |
data[].source | string | Quelle des Termins |
data[].deleted | boolean | true wenn der Termin storniert ist, sonst false |
data[].deletedAt | string (ISO 8601) | null | Zeitpunkt der Stornierung, null wenn nicht storniert |
data[].stornoReason | string | null | Storno-Grund, null wenn nicht storniert oder kein Grund angegeben |
data[].createdAt | string (ISO 8601) | null | Buchungszeitpunkt des Termins – nicht der Erstellungszeitpunkt des Datensatzes |
data[].updatedAt | string (ISO 8601) | null | Letzter Änderungszeitpunkt |
meta.total | integer | Gesamtanzahl der Termine |
meta.offset | integer | Aktuelle Startposition |
meta.limit | integer | Anzahl der zurückgegebenen Einträge |
Hinweis: Die Objekte
customerundserviceenthalten nur eine Kurzform mit den wichtigsten Feldern (siehe Tabelle oben).
Schlanker Abruf (?view=slim)
Der Query-Parameter view=slim liefert eine schlanke Payload mit nur den wichtigsten Feldern. Ohne den Parameter bleibt der Abruf unverändert – bestehende Integrationen müssen nichts anpassen.
Wird view mehrfach angegeben, gilt die letzte Angabe – ?view=default&view=slim liefert also die schlanke Antwort.
Die schlanke Payload enthält folgende Felder:
id, title, start, end, allDay, status, type, source, deleted, deletedAt, createdAt, updatedAt, customer (nur id), service (nur id), location (nur id), user (nur id), course (id, name, maxAttendees, currentAttendees)
Hinweis:
noteundstornoReasonsind im schlanken Abruf nicht enthalten.Hinweis:
courseenthält im schlanken Abruf wederattendeesnochinstructors. Wer die Teilnehmerliste braucht, ruft ohneviewab. Das macht den schlanken Abruf gleichzeitig zur datensparsameren Variante – pro Termin-Datensatz werden weniger Daten übertragen.
Beispiel: Kurs-Termin, view=slim
{
"data": [
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"title": "Yoga Anfänger",
"start": "2026-03-20T09:00:00+01:00",
"end": "2026-03-20T10:00:00+01:00",
"allDay": false,
"status": "active",
"type": "course",
"source": "oauth",
"deleted": false,
"deletedAt": null,
"createdAt": "2026-03-01T09:00:00+01:00",
"updatedAt": "2026-03-01T09:00:00+01:00",
"customer": null,
"service": null,
"location": { "id": "e5f6a7b8-c9d0-1234-efab-234567890123" },
"user": { "id": "d4e5f6a7-b8c9-0123-defa-123456789012" },
"course": {
"id": "c3d4e5f6-a7b8-9012-cdef-012345678901",
"name": "Yoga Anfänger",
"maxAttendees": 20,
"currentAttendees": 12
}
}
],
"meta": {
"total": 500,
"offset": 0,
"limit": 50
}
}curl -X GET "https://my.offisy.at/api/oauth/v2/resource/appointments?offset=0&limit=50&from=beispiel&until=beispiel&type=admin%2Ccustomer%2Ccourse&customerId=b2c3d4e5-f6a7-8901-bcde-f01234567890&view=default&updatedSince=2026-04-15T10%3A00%3A00&updatedUntil=2026-04-15T10%3A00%3A00&deletedSince=beispiel" \
-H "Authorization: Bearer {access_token}"Mögliche Fehler
| Status | Code | Beschreibung |
|---|---|---|
| 400 | validation_error | from und/oder until fehlt, obwohl weder updatedSince noch deletedSince gesetzt ist – oder der Wert ist ungültig |
| 400 | validation_error | Ungültiger Wert für view (gültige Werte: default, slim) |
| 400 | validation_error | Ein Zeitstempel-Filter enthält einen ungültigen Wert |
| 400 | validation_error | deletedSince wurde mit updatedSince oder updatedUntil kombiniert |
| 401 | invalid_client | Ungültiger oder fehlender Access Token |
| 403 | insufficient_scope | Scope appointments oder users fehlt (beide immer erforderlich) |
| 403 | insufficient_scope | ?type=course/?type=all angefragt, aber Scope customers fehlt |