API
Termine aus deinem System. Eine Anfrage, alle Kalender.
Diese Seite kannst du an ein CRM, Newsletter-Tool oder eine Agentur weitergeben. Alles ist JSON über HTTPS, keine SDKs nötig.
Grundlagen
| Feld | Typ | Bedeutung |
|---|---|---|
| Basis-URL | Text | https://calendar.digitalresults.app |
| Format | JSON im Body, Antwort JSON. Header Content-Type: application/json. | |
| Auth | Header | POST /api/events und der MCP-Server brauchen Authorization: Bearer <Token>. Jeder Kunde bekommt von Digital Results einen eigenen Token und sieht nur seine Kalender. Alle anderen Aufrufe sind ohne Token. |
| Zeiten | ISO 8601 | Immer mit Offset oder Z, z. B. 2026-11-17T09:00:00+01:00. Angezeigt wird in der Zeitzone des Termins. |
| Zeitzone | IANA | z. B. Europe/Vienna (Standard), Europe/Berlin, UTC. Gilt für Anzeige und VTIMEZONE in der .ics. |
| Fehler | JSON | { "error": "…" } mit Status 400 (Eingabe), 401 (Token), 404 (nicht gefunden). |
Termine anlegen: POST /api/events
Legt Termine in einem Kalender an oder aktualisiert sie. Gleicher uid = Update desselben Termins, Abonnenten sehen die Änderung statt eines Duplikats. Existiert der Kalender-Slug nicht, wird er mit calendarName angelegt.
Body
| Feld | Typ | Bedeutung |
|---|---|---|
| calendar | Text, Pflicht | Slug des Kalenders, z. B. bni-linz. Nur Kleinbuchstaben, Ziffern, Bindestrich. |
| calendarName | Text | Anzeigename. Nötig, wenn der Kalender noch nicht existiert. |
| timezone | IANA | Standard-Zeitzone des Kalenders. Standard Europe/Vienna. |
| events[] | Liste, Pflicht | Ein oder mehrere Termine, Felder siehe unten. |
Ein Termin in events[]
| Feld | Typ | Bedeutung |
|---|---|---|
| uid | Text | Stabile Kennung aus deinem System, z. B. workshop-2026-11-17@mein-crm. Ohne uid wird jedes Mal ein neuer Termin angelegt. |
| title | Text, Pflicht | Titel, bis 200 Zeichen. |
| start, end | ISO 8601, Pflicht | Beginn und Ende mit Offset. Ende muss nach Beginn liegen. |
| location | Text | Ort, bis 200 Zeichen. Landet im Kalendereintrag und in der Navigation. |
| description | Text | Beschreibung, bis 2000 Zeichen. Mitbringen, Parkplatz, Ansprechpartner. |
| timezone | IANA | Zeitzone dieses Termins, sonst die des Kalenders. |
| rrule | Text | Serie nach RFC 5545, z. B. FREQ=WEEKLY;BYDAY=TU;COUNT=20 oder FREQ=MONTHLY;INTERVAL=1;UNTIL=20261231T000000Z. |
| exdates | Liste ISO 8601 | Ausnahmen der Serie, jeweils der Beginn des ausfallenden Vorkommens. |
| status | confirmed | cancelled | cancelled markiert den Termin als abgesagt. Abonnenten sehen ihn durchgestrichen oder er verschwindet. |
| rsvp | true | false | Anmeldung mit Zusage und Absage vor dem Kalender-Button. Name, E-Mail und Telefon werden immer abgefragt. |
| rsvpFields | Liste | Eigene Zusatzfelder der Anmeldung, bis 12. Aufbau siehe nächster Abschnitt. |
curl -X POST https://calendar.digitalresults.app/api/events \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"calendar": "bni-linz",
"calendarName": "Termine BNI Linz",
"timezone": "Europe/Vienna",
"events": [{
"uid": "workshop-2026-11-17@mein-crm",
"title": "Workshop Linz",
"start": "2026-11-17T09:00:00+01:00",
"end": "2026-11-17T17:00:00+01:00",
"location": "Dauphinestraße 89, 4030 Linz",
"description": "Bitte Laptop mitbringen. Parkplätze hinter dem Haus.",
"rrule": "FREQ=WEEKLY;BYDAY=TU;COUNT=10",
"exdates": ["2026-12-22T09:00:00+01:00"],
"status": "confirmed",
"rsvp": true,
"rsvpFields": [
{ "id": "essen", "label": "Was möchtest du essen?", "type": "select", "options": ["Fleisch", "Vegetarisch", "Vegan"], "required": true },
{ "id": "personen", "label": "Wie viele Personen?", "type": "slider", "min": 1, "max": 6, "unit": "Personen" }
]
}]
}'Antwort
{
"calendar": {
"slug": "bni-linz", "name": "Termine BNI Linz",
"url": "https://calendar.digitalresults.app/calendar/bni-linz",
"feedUrl": "https://calendar.digitalresults.app/feed/<token>.ics",
"subscribe": { "webcal": "webcal://…", "google": "https://calendar.google.com/…", "outlook": "https://outlook.live.com/…", "office365": "https://outlook.office.com/…" }
},
"events": [{
"id": 42, "uid": "workshop-2026-11-17@mein-crm", "slug": "workshop-linz-5e830a", "sequence": 0,
"pageUrl": "https://calendar.digitalresults.app/event/workshop-linz-5e830a",
"qrUrl": "https://calendar.digitalresults.app/qr?slug=workshop-linz-5e830a",
"icsUrl": "https://calendar.digitalresults.app/event/workshop-linz-5e830a/ics",
"links": { "google": "…", "outlook": "…", "office365": "…", "msTeams": "…" },
"emailHtml": "<a href=…>In Kalender speichern</a> …",
"webHtml": "<a href=…>…</a>"
}]
}sequence zählt bei jeder Änderung hoch. pageUrl ist die Auswahlseite mit allen Kalendern (und bei rsvp der Anmeldung), qrUrl liefert ein PNG (&size=1024 für Druck), icsUrl die Datei für Apple und Outlook am PC. Zusagen und Absagen siehst du im Admin als Liste und CSV.
Zusatzfelder der Anmeldung (rsvpFields)
Bis zu 12 Felder. Jedes Feld hat id, label und type. Die Antworten landen als Spalten in der Liste und im CSV.
| Feld | Typ | Bedeutung |
|---|---|---|
| id | Text, Pflicht | Kurze Kennung ohne Leerzeichen, z. B. essen. Wird zum Spaltennamen. |
| label | Text, Pflicht | Die Frage, bis 80 Zeichen. |
| type | Pflicht | text, textarea, checkbox, select, multiselect, number, slider |
| required | true | false | Pflichtfeld bei Zusage. |
| help | Text | Hinweis unter dem Feld, bis 200 Zeichen. |
| options | Liste Text | Für select und multiselect, 2 bis 20 Optionen. |
| min, max, step | Zahl | Für number und slider. slider braucht min und max. |
| unit | Text | Einheit hinter der Zahl, z. B. Personen. |
Einmal-Link ohne Kalender: POST /api/build
Für Einladungen ohne Abo: JSON rein, Kurz-Link, QR, .ics, Kalender-Links und HTML-Snippets raus. Kein Token nötig. Das ist genau das, was der Generator macht.
| Feld | Typ | Bedeutung |
|---|---|---|
| title | Text | Überschrift des Links, z. B. der Name der Veranstaltung. |
| timezone | IANA | Standard Europe/Vienna. |
| rsvp | true | false | Anmeldung vor dem Kalender-Button. |
| rsvpFields | Liste | Wie oben. |
| events[] | Liste, Pflicht | 1 bis 20 Termine mit title, start, end, location, description. |
curl -X POST https://calendar.digitalresults.app/api/build -H "Content-Type: application/json" -d '{
"title": "BNI Workshop 2026", "timezone": "Europe/Vienna", "rsvp": true,
"events": [
{ "title": "Workshop Tag 1", "start": "2026-09-18T09:00:00+02:00", "end": "2026-09-18T17:00:00+02:00", "location": "Linz", "description": "Bitte Laptop mitbringen." },
{ "title": "Workshop Tag 2", "start": "2026-09-19T09:00:00+02:00", "end": "2026-09-19T17:00:00+02:00", "location": "Linz" }
]
}'{
"id": "PXWfS-6Y",
"pageUrl": "https://calendar.digitalresults.app/s/PXWfS-6Y",
"qrUrl": "https://calendar.digitalresults.app/qr?s=PXWfS-6Y",
"icsUrl": "https://calendar.digitalresults.app/ics?d=…",
"longUrl": "https://calendar.digitalresults.app/e?d=…",
"rsvp": true,
"answersUrl": "https://calendar.digitalresults.app/s/PXWfS-6Y/antworten?t=<geheim>",
"links": [{ "google": "…", "outlook": "…", "office365": "…", "msTeams": "…" }],
"emailHtml": "…", "webHtml": "…"
}answersUrl ist geheim und wird nur einmal ausgegeben: Zusagen und Absagen als Liste, CSV unter derselben Adresse mit .csv.
Dynamische Links ohne API
Für Newsletter- und CRM-Tools, die nur Platzhalter in URLs einsetzen können. Die Auswahlseite baut sich aus den Parametern.
https://calendar.digitalresults.app/e?title=Workshop%20Linz&start=2026-11-17T09:00&end=2026-11-17T17:00&location=Linz&description=Laptop%20mitbringen&tz=Europe/Vienna&heading=BNI%20Workshop| Feld | Typ | Bedeutung |
|---|---|---|
| title | Pflicht | Titel des Termins. |
| start, end | Pflicht | Wandzeit 2026-11-17T09:00 in der Zeitzone tz, oder ISO mit Offset. |
| location, description | Ort und Beschreibung, URL-kodiert. | |
| tz | IANA | Standard Europe/Vienna. |
| heading | Überschrift der Seite. |
Widget einbetten
Ein Script, ein Element. Das Widget rendert in einem Shadow DOM mit eigenem, neutralem Design und stört dein CSS nicht.
<script async src="https://calendar.digitalresults.app/widget.js"></script>
<!-- Einmal-Link aus /api/build oder dem Generator -->
<div data-atc-id="PXWfS-6Y"></div>
<!-- Kalender-Termin aus /api/events (slug aus der Antwort) -->
<div data-atc-event="workshop-linz-5e830a"></div>
<!-- Dynamisch, ohne Vorab-Registrierung -->
<div data-atc-title="Workshop Linz" data-atc-start="2026-11-17T09:00" data-atc-end="2026-11-17T17:00"
data-atc-location="Linz" data-atc-description="Laptop mitbringen" data-atc-tz="Europe/Vienna"></div>Die Daten dahinter liefert GET /api/widget?id=… bzw. ?slug=… als JSON mit CORS, falls du das Menü selbst bauen willst. Für Terminreihen gibt es zusätzlich den Monatskalender als iframe: https://calendar.digitalresults.app/embed/calendar/<slug>.
Feeds, Seiten, QR
| Feld | Typ | Bedeutung |
|---|---|---|
| /calendar/<slug> | Seite | Kalender-Landingpage mit kommenden Terminen und Abo-Buttons (Apple, Outlook, Google, Andere). |
| /feed/<token>.ics | Feed | Abo-Feed mit allen Terminen, Serien, Ausnahmen, Absagen. Token kommt aus der Antwort von /api/events. Cache 5 Minuten. |
| /event/<slug> | Seite | Auswahlseite eines Kalender-Termins, mit Anmeldung, wenn rsvp aktiv. |
| /event/<slug>/ics | Datei | .ics für Apple, Outlook am PC, Thunderbird. |
| /s/<id> | Seite | Auswahlseite eines Einmal-Links. |
| /qr?slug=… · /qr?s=… | PNG | QR-Code auf die jeweilige Seite. &size=1024 für Druck. |
| /embed/calendar/<slug> | iframe | Monatskalender aller Termine mit Klick zur Termin-Seite. |
Anmeldungen: POST /api/rsvp
Nur nötig, wenn du ein eigenes Formular baust. Die Termin-Seite erledigt das sonst selbst.
| Feld | Typ | Bedeutung |
|---|---|---|
| slug | link | Pflicht | slug des Kalender-Termins oder id des Einmal-Links. |
| status | yes | no | Zusage oder Absage. |
| name, email | Pflicht | Gleiche E-Mail ersetzt die vorherige Antwort. |
| phone | Optional. | |
| answers | Objekt | Antworten auf rsvpFields, Schlüssel = id des Felds. Wird nur bei yes geprüft. |
curl -X POST https://calendar.digitalresults.app/api/rsvp -H "Content-Type: application/json" -d '{ "slug": "workshop-linz-5e830a", "status": "yes", "name": "Anna Muster", "email": "anna@muster.at", "phone": "+43 664 0000000", "answers": { "essen": "Vegetarisch", "personen": 2 } }'KI-Agenten: MCP-Server
Der Kalender ist ein MCP-Server (Model Context Protocol, Streamable HTTP). Claude Code, Claude Desktop, ChatGPT, Cursor und andere Agenten bekommen damit dieselben Möglichkeiten wie die REST-API, als Tools mit Beschreibung. Auth wie bei /api/events: Bearer-Token.
claude mcp add --transport http calendar https://calendar.digitalresults.app/api/mcp --header "Authorization: Bearer <API_TOKEN>"{
"mcpServers": {
"calendar": {
"url": "https://calendar.digitalresults.app/api/mcp",
"headers": { "Authorization": "Bearer <API_TOKEN>" }
}
}
}{ "mcpServers": { "calendar": { "command": "npx", "args": ["-y", "mcp-remote", "https://calendar.digitalresults.app/api/mcp", "--header", "Authorization: Bearer <API_TOKEN>"] } } }Tools
| Feld | Typ | Bedeutung |
|---|---|---|
| upsert_events | calendar, calendarName?, timezone?, events[] | Wie POST /api/events. Termine anlegen oder per uid aktualisieren, Kalender bei Bedarf anlegen. Liefert Links, QR, .ics, Feed. |
| build_link | title?, timezone?, rsvp?, rsvpFields?, events[] | Wie POST /api/build. Einmal-Link mit Auswahlseite, QR und Kalender-Links. |
| list_calendars | Alle Kalender mit Slug, Name, Zeitzone, Seite, Feed-URL. | |
| list_events | calendar, includePast? | Termine eines Kalenders mit uid, slug, Zeiten, Status, Serie, Termin-Link. |
| cancel_event | calendar, uid | slug | Termin absagen. Abonnenten sehen die Absage beim nächsten Sync. |
| list_rsvps | slug | link | Zusagen und Absagen mit Zähler, Name, E-Mail, Telefon und Antworten auf Zusatzfelder. |
Beispiel-Anweisung an den Agenten: „Leg für den 17. November 9 bis 17 Uhr den Workshop Linz in den Kalender bni-linz, Ort Dauphinestraße 89, mit Anmeldung, und gib mir Termin-Link und QR.“
Grenzen
- 1 bis 20 Termine pro Einmal-Link, Titel und Ort bis 200 Zeichen, Beschreibung bis 2000 Zeichen.
- Google, Outlook.com, Microsoft 365 und Teams legen pro Klick einen Termin an. Mehrere Termine auf einmal nur per .ics oder Abo.
- Abo-Sync liegt bei den Apps: Apple bis stündlich, Outlook etwa alle 3 Stunden, Google bis 24 Stunden.
- Feeds sind ohne Login abrufbar, geschützt nur durch den langen Token in der URL.
- Es gibt keine Bestätigungs-Mails an Teilnehmer. Antworten holst du im Admin oder über answersUrl.