# API: Events und Anmeldungen

Events lesen, anlegen und ändern, Tickets und Anmeldungen lesen, CSV und Kennzahlen.

## Hinweise für KI-Agenten

- API-Basisadresse: `https://api.eventwerk.eu/api/v1`
- OpenAPI 3.1: `https://api.eventwerk.eu/openapi.json` und `/api/v1/openapi.json`. Daraus lassen sich Clients und Typprüfungen generieren.
- Anmeldung: `Authorization: Bearer ew_...`
- Schlüssel: Einstellungen unter `/dashboard/einstellungen`, nur mit Eventwerk Plus und nur für Eigentümer. Der volle Schlüssel wird nur einmal angezeigt. Name und Zeitpunkt der letzten Nutzung werden geführt und ausgegeben.
- Zeiten: Alle Zeitpunkte kommen in UTC mit Suffix `Z`. Die Zeitzone des Events steht getrennt in `timezone` als IANA-Name, Standard `Europe/Vienna`. Clients rechnen selbst um.
- Paginierung: Die Event- und Anmeldelisten liefern `{items, has_more, next_cursor}` mit `limit` (Standard 50, höchstens 200), `cursor` und `updated_since`.
- Grenzen: 200 Anfragen pro Minute je Schlüssel. Der Zähler liegt im Speicher je Instanz, ist hinter mehreren Instanzen ein Vielfaches und beginnt nach einem Deploy neu. Bei Überschreitung antwortet die API mit 429, `rate_limited` und `Retry-After`.
- Gastgrenzen: Anmelde-Link 20 je Stunde je IP-Adresse, Anmeldung 60 je Stunde je IP-Adresse und 3 je Stunde je E-Mail-Adresse und Event.
- Fehler: JSON mit `code` und `message`, optional mit `details`.

Die Endpunkte unter `/events` bilden die Verwaltung für einen API-Schlüssel ab. Ein Schlüssel sieht und ändert nur Events seiner eigenen Organisation. Alle Zeitpunkte kommen in UTC mit Suffix `Z`; die Zeitzone des Events steht getrennt in `timezone`.

## Event-Objekt

| Feld | Typ | Bedeutung |
| --- | --- | --- |
| id | string | Kennung des Events. |
| org_id | string | Kennung der Organisation. |
| slug | string | Teil der öffentlichen Adresse `/e/<slug>`. |
| title | string | Titel. |
| description | string | Beschreibung. |
| cover_url | string | Bildadresse. |
| start_at | string | Beginn als RFC-3339-Zeitpunkt in UTC mit Suffix Z. |
| end_at | string oder null | Ende als RFC-3339-Zeitpunkt in UTC mit Suffix Z. |
| timezone | string | IANA-Zeitzone des Events, Standard Europe/Vienna. Gilt für die Darstellung, nicht für die übertragenen Zeitpunkte. |
| location_type | string | offline, online oder hybrid. |
| location_name | string | Name des Ortes. |
| location_address | string | Adresse. |
| online_url | string | Adresse für Online- oder Hybrid-Veranstaltungen. |
| visibility | string | public oder private. |
| status | string | draft oder published. |
| capacity | number oder null | Gesamtkapazität. |
| category | string | Kategorie oder leer. |
| tags | string[] | Schlagworte. |
| latitude | number oder null | Breitengrad. |
| longitude | number oder null | Längengrad. |
| series_id | string oder null | Kennung der Serie. |
| series_index | number | Position in der Serie. |
| created_at | string | Anlagezeitpunkt in UTC mit Suffix Z. |
| updated_at | string | Änderungszeitpunkt in UTC mit Suffix Z. |

Die Eventliste ergänzt `registration_count` mit der Zahl aller Anmeldungen. Ein einzelnes Event enthält dieses Feld nicht.

## Events lesen

```bash
curl -s https://api.eventwerk.eu/api/v1/events \
  -H "Authorization: Bearer ew_..."

curl -s https://api.eventwerk.eu/api/v1/events/<eventID> \
  -H "Authorization: Bearer ew_..."
```

Die Liste antwortet mit `{ "items": [...], "has_more": false, "next_cursor": null }` und ist nach Beginn aufsteigend sortiert. Ein einzelnes Event antwortet direkt mit dem Event-Objekt.

| Parameter | Bedeutung |
| --- | --- |
| limit | Optional. Seitengröße zwischen 1 und 200. Standard 50. |
| cursor | Optional. Cursor aus `next_cursor` der vorherigen Antwort. |
| updated_since | Optional. RFC-3339-Zeitpunkt. Liefert nur Events, die danach geändert wurden. |

`has_more` sagt, ob weitere Datensätze folgen. `next_cursor` enthält den Cursor der nächsten Seite oder null. Der Cursor ist undurchsichtig und gehört immer zu derselben Liste.

## Event anlegen

Pflicht sind `title`, `start_at` und `timezone`. Ohne `location_type` gilt offline, ohne `visibility` private, ohne `status` draft. Die Zeit muss als RFC-3339-String mit Zeitzone vorliegen. `status: published` veröffentlicht das Event sofort, anders als die Anlage ohne Konto, die auf die Bestätigung der E-Mail-Adresse wartet.

```json
{
  "title": "Herbstkonferenz",
  "description": "Ein Tag für Austausch und Workshops.",
  "start_at": "2026-11-12T09:00:00+01:00",
  "end_at": "2026-11-12T17:00:00+01:00",
  "timezone": "Europe/Vienna",
  "location_type": "offline",
  "location_name": "Kulturhaus",
  "location_address": "Hauptplatz 1, 8010 Graz",
  "visibility": "public",
  "status": "published",
  "category": "konferenz",
  "tags": ["austausch", "workshops"]
}
```

```bash
curl -s -X POST https://api.eventwerk.eu/api/v1/events \
  -H "Authorization: Bearer ew_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f2c1a9e-4b3d-4c8a-9f10-2e6d5b7a1c30" \
  -d @event.json
```

Die Antwort ist das angelegte Event mit Status 201. Der Header `Location` nennt den Pfad zum Event. Jedes Event erhält automatisch eine freie Ticketart Standard, wenn keine Ticketart angegeben wurde.

Mit dem optionalen Header `Idempotency-Key` lässt sich die Anlage wiederholen. Gleicher Schlüssel und gleicher Rumpf liefern dieselbe Antwort erneut. Ein anderer Rumpf mit demselben Schlüssel ergibt Status 409 und den Code `idempotency_conflict`. Ohne Header legt jeder Aufruf ein neues Event an.

- Der Titel darf höchstens 200 Zeichen lang sein.
- Das Ende darf nicht vor dem Beginn liegen.
- Die Zeitzone muss eine gültige IANA-Zeitzone sein.
- Die Kapazität darf nicht negativ sein.
- Es sind höchstens 10 Schlagworte mit je höchstens 40 Zeichen erlaubt.
- Ein unbekanntes Feld im Rumpf ergibt `unknown_field`; `details.field` nennt den Namen.

## Event ändern

`PATCH /events/{eventID}` ändert nur die übergebenen Felder. Mindestens ein Feld muss gesetzt sein, sonst antwortet die API mit `no_changes`. Ein `end_at` von null entfernt das Ende.

```bash
curl -s -X PATCH https://api.eventwerk.eu/api/v1/events/<eventID> \
  -H "Authorization: Bearer ew_..." \
  -H "Content-Type: application/json" \
  -d '{"title":"Herbstkonferenz 2026"}'
```

## Event löschen

`DELETE /events/{eventID}` löscht das Event und alle zugehörigen Anmeldungen. Die Antwort hat Status 200 und enthält eine Meldung. Ein unbekanntes Event ergibt `event_not_found`. Ein veröffentlichtes Event mit Anmeldungen lässt sich nicht löschen; die API antwortet mit Status 409, dem Code `event_has_registrations` und `details.count`.

## Ticketarten lesen

`GET /events/{eventID}/tickets` liefert `{ "tickets": [...] }`. Jede Ticketart enthält `id`, `event_id`, `name`, `description`, `price_cents`, `currency`, `quantity`, `min_per_order`, `max_per_order`, `hidden`, `is_donation`, `active`, `sort_order` und `created_at`.

> **Hinweis:** Die öffentliche API kann Ticketarten nur lesen. Anlegen und Ändern erfolgt im Dashboard.

## Anmeldungen lesen

`GET /events/{eventID}/registrations` liefert `{ "items": [...], "has_more": false, "next_cursor": null }`. Die Liste ist nach Anmeldung aufsteigend sortiert.

| Parameter | Bedeutung |
| --- | --- |
| status | Optional. pending, confirmed, waitlisted, cancelled oder declined. |
| query | Optional. Sucht ohne Beachtung der Großschreibung in Name und E-Mail-Adresse. |
| limit | Optional. Seitengröße zwischen 1 und 200. Standard 50. |
| cursor | Optional. Cursor aus `next_cursor` der vorherigen Antwort. |
| updated_since | Optional. RFC-3339-Zeitpunkt. Liefert nur Anmeldungen, die danach geändert wurden. |

`has_more` sagt, ob weitere Datensätze folgen. `next_cursor` enthält den Cursor der nächsten Seite oder null. Der Cursor ist undurchsichtig und gehört immer zu derselben Liste.

Jede Anmeldung enthält `id`, `name`, `email`, `status`, `ticket_type_id`, `price_paid_cents`, `checked_in_at` und `created_at`. Zeitpunkte kommen in UTC mit Suffix `Z`. Der QR-Token ist nicht in der Liste enthalten.

```bash
curl -s "https://api.eventwerk.eu/api/v1/events/<eventID>/registrations?status=confirmed&query=Anna" \
  -H "Authorization: Bearer ew_..."
```

## CSV-Export

`GET /events/{eventID}/registrations.csv` liefert eine CSV-Datei mit Semikolon als Trennzeichen und einem Byte Order Mark am Anfang. Die Spalten entsprechen dem Export im Dashboard, einschließlich der Antworten auf eigene Fragen.

## Kennzahlen

`GET /events/{eventID}/stats?days=30` liefert Kennzahlen. `days` liegt zwischen 1 und 365, Standard 30. Aufrufe gelten für den Zeitraum, die Anmeldezahlen für das gesamte Event.

| Feld | Bedeutung |
| --- | --- |
| views_total | Aufrufe der öffentlichen Seite im Zeitraum. |
| views_by_day | Aufrufe je Tag. |
| registrations | Alle Anmeldungen. |
| confirmed | Bestätigte Anmeldungen. |
| pending | Anmeldungen, die auf Freigabe warten. |
| waitlisted | Anmeldungen auf der Warteliste. |
| cancelled | Stornierte Anmeldungen. |
| checked_in | Eingecheckte Anmeldungen. |
| conversion_rate | Anmeldungen geteilt durch Aufrufe, zwischen 0 und 1. |
| checkin_rate | Eingecheckte geteilt durch bestätigte Anmeldungen, zwischen 0 und 1. |

> **Hinweis:** Serien, Duplizieren, Nachrichten, Umfragen, Programm, Erinnerungen, CSV-Import und Empfehlungscodes sind nicht Teil der öffentlichen API.
