API
Mit der API können deine eigenen Anwendungen – z. B. deine Website oder dein Ticketsystem – die Events eines Profils lesen, erstellen und bearbeiten. Die API gilt immer für genau ein Profil und nur für Events, deren Eigentümer dieses Profil ist.
Erste Schritte
- Wechsle zu dem Profil, für das du die API nutzen willst.
- Öffne Einstellungen → API und erstelle ein Token. Nur der Eigentümer eines Profils kann Tokens erstellen.
- Kopiere das Token sofort – es wird nur einmal angezeigt. Behandle es vertraulich wie ein Passwort.
Schicke das Token bei jeder Anfrage im Header Authorization mit. Alle Adressen beginnen mit /api/v1 unter https://upcoming.at:
curl https://upcoming.at/api/v1/events \
-H "Authorization: Bearer 1|AbCdEf…"
In den Beispielen steht $TOKEN für dein Token.
Berechtigungen und Grenzen
- Nur lesen: Events, Orte und Tags abrufen.
- Lesen und schreiben: zusätzlich Events erstellen und bearbeiten sowie Orte speichern. Dafür muss das Profil freigeschaltet sein: Beantrage den Schreibzugriff unter Einstellungen → API und beschreibe kurz, wofür du die API nutzt. Nach der Prüfung durch das Upcoming-Team bekommst du eine Benachrichtigung. Lesen geht sofort.
- Events anderer Profile – auch solche, bei denen dein Profil nur Organisator ist – gibt es für die API nicht (Antwort
404). - Höchstens 60 Anfragen pro Minute und Profil.
- Gesperrte Profile können die API nicht nutzen. Wird ein Profil übergeben, verlieren alle seine Tokens die Gültigkeit.
- Löschen von Events, Sub-Events und weitere Organisatoren sind (noch) nicht Teil der API – das geht weiterhin auf der Website.
Die Sprache von Fehlermeldungen und Namen richtet sich nach dem Header Accept-Language (de-DE oder en-US), ohne ihn nach der Sprache des Eigentümer-Kontos.
Events auflisten
GET /api/v1/events – die Events des Profils, nach Beginn sortiert, 25 pro Seite.
| Parameter | Bedeutung |
|---|---|
from, to |
Nur Events, die in diesem Zeitraum beginnen (ISO 8601) |
updated_since |
Nur Events, die seitdem geändert wurden – praktisch zum Abgleichen |
per_page |
1–100 Events pro Seite |
page |
Seite |
curl "https://upcoming.at/api/v1/events?from=2026-12-01T00:00:00%2B01:00" \
-H "Authorization: Bearer $TOKEN"
Die Antwort enthält die Events in data, dazu links und meta (u. a. total, last_page) zum Blättern.
Ein Event abrufen
GET /api/v1/events/{uuid}
{
"data": {
"uuid": "9d4c…",
"url": "https://upcoming.at/events/9d4c…",
"title": "Sommerkonzert",
"description": "Open Air im Park",
"event_type": "in_person",
"visibility": "public",
"startdatetime": "2026-07-04T19:00:00+02:00",
"enddatetime": "2026-07-04T22:00:00+02:00",
"startdatetime_utc": "2026-07-04T17:00:00+00:00",
"enddatetime_utc": "2026-07-04T20:00:00+00:00",
"timezone": "Europe/Berlin",
"is_all_day": false,
"place_id": 42,
"place": { "id": 42, "type": "venue", "name": "Stadtpark", "address": "Parkweg 1", "postal_code": "90402", "city": "Nürnberg", "country": "Deutschland", "latitude": 49.45, "longitude": 11.08, "url": "…" },
"online_url": null,
"online_instructions": null,
"photo_urls": null,
"options": { "links": [], "allow_comments": true, "allow_invitations": true, "organizers_can_duplicate": false, "allow_sub_events": false, "max_attendees": null, "ticket_price": "15 €" },
"tags": [{ "id": 3, "slug": "konzert", "name": "Konzert", "names": { "de-DE": "Konzert", "en-US": "Concert" } }],
"labels": [{ "id": 7, "name": "Sommer" }],
"parent_uuid": null,
"attendance_counts": { "going": 12, "interested": 30, "invited": 4 },
"created_at": "2026-05-01T10:00:00+00:00",
"updated_at": "2026-05-02T08:30:00+00:00"
}
}
Event erstellen
POST /api/v1/events – Eigentümer ist immer das Profil des Tokens. Antwort: 201 mit dem Event.
curl -X POST "https://upcoming.at/api/v1/events" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Sommerkonzert",
"startdatetime": "2026-07-04T19:00:00",
"enddatetime": "2026-07-04T22:00:00",
"timezone": "Europe/Berlin",
"place_id": 42,
"tag_ids": [3],
"new_labels": ["Sommer"]
}'
| Feld | Bedeutung |
|---|---|
title |
Pflicht, max. 255 Zeichen |
description |
Text, max. 10.000 Zeichen |
event_type |
in_person (Standard), online oder hybrid |
startdatetime |
Pflicht. Beginn, siehe „Datum und Zeitzone“ |
enddatetime |
Ende, nach dem Beginn |
timezone |
Pflicht, z. B. Europe/Berlin |
is_all_day |
Ganztägig (true/false), siehe „Datum und Zeitzone“ |
visibility |
public (Standard), followers, unlisted oder private |
place_id |
Ort, siehe „Orte“ |
online_url |
Pflicht bei online und hybrid |
online_instructions |
Hinweise zur Teilnahme online |
is_organizer |
Profil als Organisator anzeigen (Standard true) |
tag_ids |
IDs aus „Tags“, höchstens so viele wie auf der Website |
label_ids, new_labels |
Eigene Labels des Profils: vorhandene IDs und neue Namen |
photo |
Bild (JPG, PNG, WebP, GIF, HEIC), nur per multipart/form-data |
options.links |
Bis zu 5 Links: { "url": "…", "title": "…", "type": "ticket" } |
options.ticket_price |
Preis als Text, z. B. "15 €" |
options.max_attendees |
Höchstzahl Teilnehmender |
options.allow_comments, options.allow_invitations |
Kommentare bzw. Einladungen erlauben (Standard true) |
Mit Foto sendest du die Felder als Formular:
curl -X POST "https://upcoming.at/api/v1/events" \
-H "Authorization: Bearer $TOKEN" \
-F title="Sommerkonzert" \
-F startdatetime="2026-07-04T19:00:00" \
-F timezone="Europe/Berlin" \
-F photo=@plakat.jpg
Event bearbeiten
PATCH /api/v1/events/{uuid} – nur die gesendeten Felder ändern sich, auch bei options. Tags und Labels bleiben, wenn du tag_ids bzw. label_ids/new_labels nicht sendest; gesendet ersetzen sie die bisherigen ("tag_ids": [] entfernt alle Tags). "remove_photo": true entfernt das Foto.
curl -X PATCH "https://upcoming.at/api/v1/events/9d4c…" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "startdatetime": "2026-07-05T19:00:00" }'
Ändern sich Datum, Uhrzeit oder Ort, werden Teilnehmende und Eingeladene benachrichtigt – wie auf der Website.
Ein neues Foto lädst du per POST mit _method=PATCH hoch (PHP nimmt Dateien nur bei POST an):
curl -X POST "https://upcoming.at/api/v1/events/9d4c…" \
-H "Authorization: Bearer $TOKEN" \
-F _method=PATCH \
-F photo=@plakat.jpg
Datum und Zeitzone
Zeiten gibst du im ISO-8601-Format an:
- ohne Offset (
2026-07-04T19:00:00): gilt als Ortszeit intimezonedes Events, - mit Offset (
2026-07-04T19:00:00+02:00): der Zeitpunkt zählt, egal welche Zeitzone das Event hat.
Antworten enthalten beides: die Ortszeit mit Offset (startdatetime) und UTC (startdatetime_utc).
Ganztägige Events: Mit "is_all_day": true reicht das Datum (2026-07-04). Mitgesendete Uhrzeiten werden ignoriert: Beginn ist 00:00, Ende 23:59 Ortszeit. Für ein eintägiges Event lässt du enddatetime weg, für mehrere Tage gibst du den letzten Tag an:
{ "is_all_day": true, "startdatetime": "2026-07-04", "enddatetime": "2026-07-06", "timezone": "Europe/Berlin" }
Orte
Events verweisen per place_id auf einen Ort. Suche ihn zuerst:
GET /api/v1/places?query=Stadtpark (optional limit, 1–20)
Die Ergebnisse enthalten zuerst Orte, die es auf Upcoming schon gibt ("source": "local", mit id), danach weitere Treffer der Ortssuche ("source": "geoapify", id ist null). Einen solchen Treffer speicherst du, indem du ihn unverändert sendest:
curl -X POST "https://upcoming.at/api/v1/places" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "geoapify_place_id": "51a3…", "name": "Stadtpark", "address": "Parkweg 1", "city": "Nürnberg", "country": "Deutschland", "postal_code": "90402", "latitude": 49.45, "longitude": 11.08, "type": "venue" }'
Die Antwort enthält den Ort mit seiner id – 201, wenn er neu ist, 200, wenn es ihn schon gab.
Tags
GET /api/v1/tags – alle Tags, die du Events geben kannst, mit id, slug, name und den Namen je Sprache (names).
Fehler
| Status | Bedeutung |
|---|---|
401 |
Kein oder ungültiges Token |
403 |
Token darf das nicht (z. B. nur lesen), Schreibzugriff ist nicht freigeschaltet, oder das Profil ist gesperrt |
404 |
Event nicht vorhanden oder gehört nicht zu deinem Profil |
422 |
Ungültige Daten, Details in errors |
429 |
Zu viele Anfragen, kurz warten |
{
"message": "Title ist erforderlich.",
"errors": { "title": ["Title ist erforderlich."] }
}
Versionen
Innerhalb von v1 kommen höchstens neue Felder und Adressen hinzu. Felder werden nicht umbenannt oder entfernt, und ihre Bedeutung ändert sich nicht. Größere Änderungen erscheinen als v2, v1 bleibt dann eine Zeit lang erhalten.