API
With the API, your own applications – e.g. your website or ticket system – can read, create and edit the events of a profile. The API always works for exactly one profile and only for events owned by that profile.
Getting started
- Switch to the profile you want to use the API for.
- Open Settings → API and create a token. Only the owner of a profile can create tokens.
- Copy the token right away – it is shown only once. Keep it secret like a password.
Send the token with every request in the Authorization header. All paths start with /api/v1 on https://upcoming.at:
curl https://upcoming.at/api/v1/events \
-H "Authorization: Bearer 1|AbCdEf…"
In the examples, $TOKEN stands for your token.
Permissions and limits
- Read only: fetch events, places and tags.
- Read and write: additionally create and edit events and store places. This needs an approved profile: request write access under Settings → API and briefly describe what you use the API for. You get a notification once the Upcoming team has reviewed it. Reading works right away.
- Events of other profiles – including those your profile only organizes – do not exist for the API (response
404). - At most 60 requests per minute per profile.
- Blocked profiles cannot use the API. When a profile is handed over, all its tokens stop working.
- Deleting events, sub-events and further organizers are not part of the API (yet) – use the website for those.
Error messages and names follow the Accept-Language header (de-DE or en-US), without it the language of the owner's account.
List events
GET /api/v1/events – the profile's events, by start, 25 per page.
| Parameter | Meaning |
|---|---|
from, to |
Only events starting in this period (ISO 8601) |
updated_since |
Only events changed since then – handy for syncing |
per_page |
1–100 events per page |
page |
Page |
curl "https://upcoming.at/api/v1/events?from=2026-12-01T00:00:00%2B01:00" \
-H "Authorization: Bearer $TOKEN"
The response contains the events in data, plus links and meta (including total, last_page) for paging.
Get an event
GET /api/v1/events/{uuid}
{
"data": {
"uuid": "9d4c…",
"url": "https://upcoming.at/events/9d4c…",
"title": "Summer concert",
"description": "Open air in the 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": "Nuremberg", "country": "Germany", "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": "Concert", "names": { "de-DE": "Konzert", "en-US": "Concert" } }],
"labels": [{ "id": 7, "name": "Summer" }],
"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"
}
}
Create an event
POST /api/v1/events – the token's profile always owns the event. Response: 201 with the event.
curl -X POST "https://upcoming.at/api/v1/events" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Summer concert",
"startdatetime": "2026-07-04T19:00:00",
"enddatetime": "2026-07-04T22:00:00",
"timezone": "Europe/Berlin",
"place_id": 42,
"tag_ids": [3],
"new_labels": ["Summer"]
}'
| Field | Meaning |
|---|---|
title |
Required, max. 255 characters |
description |
Text, max. 10,000 characters |
event_type |
in_person (default), online or hybrid |
startdatetime |
Required. Start, see "Date and time zone" |
enddatetime |
End, after the start |
timezone |
Required, e.g. Europe/Berlin |
is_all_day |
All day (true/false), see "Date and time zone" |
visibility |
public (default), followers, unlisted or private |
place_id |
Place, see "Places" |
online_url |
Required for online and hybrid |
online_instructions |
Notes on joining online |
is_organizer |
Show the profile as organizer (default true) |
tag_ids |
IDs from "Tags", at most as many as on the website |
label_ids, new_labels |
The profile's own labels: existing IDs and new names |
photo |
Image (JPG, PNG, WebP, GIF, HEIC), only via multipart/form-data |
options.links |
Up to 5 links: { "url": "…", "title": "…", "type": "ticket" } |
options.ticket_price |
Price as text, e.g. "15 €" |
options.max_attendees |
Maximum number of attendees |
options.allow_comments, options.allow_invitations |
Allow comments or invitations (default true) |
With a photo, send the fields as a form:
curl -X POST "https://upcoming.at/api/v1/events" \
-H "Authorization: Bearer $TOKEN" \
-F title="Summer concert" \
-F startdatetime="2026-07-04T19:00:00" \
-F timezone="Europe/Berlin" \
-F photo=@poster.jpg
Edit an event
PATCH /api/v1/events/{uuid} – only the fields sent change, also within options. Tags and labels stay when you don't send tag_ids or label_ids/new_labels; when sent, they replace the previous ones ("tag_ids": [] removes all tags). "remove_photo": true removes the photo.
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" }'
When the date, time or place changes, attendees and invitees are notified – as on the website.
Upload a new photo with POST and _method=PATCH (PHP only accepts files with POST):
curl -X POST "https://upcoming.at/api/v1/events/9d4c…" \
-H "Authorization: Bearer $TOKEN" \
-F _method=PATCH \
-F photo=@poster.jpg
Date and time zone
Give times in ISO 8601 format:
- without offset (
2026-07-04T19:00:00): local time in the event'stimezone, - with offset (
2026-07-04T19:00:00+02:00): that point in time, whatever the event's time zone.
Responses contain both: the local time with offset (startdatetime) and UTC (startdatetime_utc).
All-day events: With "is_all_day": true, the date is enough (2026-07-04). Times sent along are ignored: the event starts at 00:00 and ends at 23:59 local time. For a one-day event leave out enddatetime; for several days give the last day:
{ "is_all_day": true, "startdatetime": "2026-07-04", "enddatetime": "2026-07-06", "timezone": "Europe/Berlin" }
Places
Events refer to a place via place_id. Search for it first:
GET /api/v1/places?query=Stadtpark (optional limit, 1–20)
The results list places already on Upcoming first ("source": "local", with id), then further hits of the place search ("source": "geoapify", id is null). Store such a hit by sending it unchanged:
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": "Nuremberg", "country": "Germany", "postal_code": "90402", "latitude": 49.45, "longitude": 11.08, "type": "venue" }'
The response contains the place with its id – 201 if it is new, 200 if it already existed.
Tags
GET /api/v1/tags – all tags you can give events, with id, slug, name and the names per language (names).
Errors
| Status | Meaning |
|---|---|
401 |
No or invalid token |
403 |
The token may not do this (e.g. read only), write access is not approved, or the profile is blocked |
404 |
The event does not exist or does not belong to your profile |
422 |
Invalid data, details in errors |
429 |
Too many requests, wait a moment |
{
"message": "The title field is required.",
"errors": { "title": ["The title field is required."] }
}
Versions
Within v1, only new fields and paths are added. Fields are not renamed or removed, and their meaning does not change. Bigger changes appear as v2; v1 then stays available for a while.