Logo

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

  1. Switch to the profile you want to use the API for.
  2. Open Settings → API and create a token. Only the owner of a profile can create tokens.
  3. 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's timezone,
  • 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.