UltimateTheme Park
API reference▾

API reference

Your park, from your own code.

Every UTP Connect answers at its own address, under /api/v1: rides live, shows, laps, on-ride photos — and webhooks that tell your bot when a ride stops or a photo is taken.

Not writing the code yourself? Start with Your park's API: how to make a key, and what to hand to whoever builds your tool.

Introduction

Read your park from outside the game — its rides, its shows, its riders and their on-ride photos — and start a show or play a track from your own website, bot or tools.

Where it lives. Every park hosts its own instance — the site its guests open on their phones — and the API is at that address, followed by /api/v1. The instance also serves this document, at /api/v1/openapi.json, to import into Postman, Insomnia or a code generator.

Keys. Create a key in Administration → Settings → API and send it with every request, as Authorization: Bearer utp_…. It is shown once. A Read key reads; a Read and write key can also start shows and play audio. Keep keys on a server, never in a web page anybody can open. A park holds 20 keys at most.

Limits. 120 requests a minute per key; every answer says how many are left in RateLimit-Remaining. Past that, a 429 says when to try again in Retry-After.

Errors come as { "error": "code", "message": "…" }: test the code, show the message.

Webhooks. To hear about laps, photos, emergency stops, shows and the server coming and going, rather than ask: add a webhook in Administration → Settings → API. See Webhooks below. A Discord channel's webhook link is sent a message for the channel instead of the envelope described there: no program needed in between.

Download the specification (OpenAPI 3.1)

Park

Everything live, in one call.

GET/api/v1/park/live

The park, live

Every ride and show as they are right now, in one call: what a status page needs. Asking more than once every five seconds tells you nothing new.

Answer · 200

FieldType
parkobject
park.name

Your park's name, from the administration.

string
park.serverOnline

Whether the Minecraft server is connected.

boolean
park.guestsListening

Players with their audio page open.

integer
park.timeZone

The park's time zone, which the shows' schedules are written in. Null until a show is scheduled.

stringor null
ridesRide[]
rides[].id

The ride's number, as /utp ride list shows it.

integer
rides[].namestring
rides[].typecoaster | flat
rides[].availability

Whether the ride takes guests, as its owner set it. open. closed, maintenance and broken all close it to guests — no guest boards, nobody takes the operator's post — and say why; under maintenance, staff allowed to test the ride may still ride it and run it from its post, and those laps are not counted. Kept while the Minecraft server is offline.

open | closed | maintenance | broken
rides[].status

operating: running as usual. down: stopped by an emergency stop, until somebody releases it. closed: the Minecraft server is not connected, or the ride is not set up in the world. Treat any other value as unknown: more may come.

operating | down | closed
rides[].phase

Null while the ride is closed.

idle | dispatching | running | unloading | transferor null
rides[].manoeuvre

The ride working itself, or null: restart brings its trains out of storage, respawn puts its one train back in place — a coaster's in its station, a flat ride's seats — and hand-back takes over from an operator who left.

restart | hand-back | respawnor null
rides[].operator

Who holds the operator's post, or null when the ride runs by itself.

Playeror null
rides[].operator.uuid

The player's Minecraft UUID.

string
rides[].operator.name

The name they were last seen with.

string
rides[].guestsInStation

Players standing in the station's region right now: the nearest thing to a queue. Null while closed.

integeror null
rides[].trainInStation

Null while closed.

booleanor null
rides[].lapsToday

Laps ridden today (UTC), every rider counted.

integer
showsShow[]
shows[].id

The show's number, as /utp sequence list shows it.

integer
shows[].namestring
shows[].region

The WorldGuard region its music is heard in, or null.

stringor null
shows[].location

Where guests find it, as the owner wrote it: Main Lake. Null for a show kept off the guests' schedule.

stringor null
shows[].music

The track it plays to, or null for a show without music.

objector null
shows[].music.titlestring
shows[].music.authorstringor null
shows[].duration

In seconds: the music's length or the last cue, whichever ends later.

numberor null
shows[].runningboolean
shows[].startedAt

When the run under way started, or null.

date-timeor null
shows[].position

Seconds into the show, while it runs.

numberor null
shows[].schedule

When it starts by itself, as set in Administration → Sequences. Empty for a show started by hand.

ShowSchedule[]
shows[].schedule[].days

The days of the week it starts on.

monday | tuesday | wednesday | thursday | friday | saturday | sunday[]
shows[].schedule[].time

HH:MM, on the park's clocks: 19:00 in timeZone.

string
shows[].schedule[].timeZone

The park's time zone, by its IANA name: Europe/Paris.

string
shows[].schedule[].from

The first day it runs, or null.

stringor null
shows[].schedule[].until

The last day it runs, or null. The same as from for a single day.

stringor null
shows[].schedule[].enabled

False for a time switched off in the administration: kept, but it starts nothing.

boolean
shows[].nextRunAt

When it next starts on its schedule, or null. It starts only if somebody is online then.

date-timeor null
updatedAt

An instant, in ISO 8601, UTC.

date-time

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 429rate_limitedMore than 120 requests in a minute with this key.
  • 504server_timeoutThe Minecraft server is connected but did not answer within five seconds.
Request
curl https://connect.your-park.com/api/v1/park/live \
  -H "Authorization: Bearer $UTP_API_KEY"
Answer · 200
{
  "park": {
    "name": "Wonder Park",
    "serverOnline": true,
    "guestsListening": 42,
    "timeZone": "Europe/Paris"
  },
  "rides": [
    {
      "id": 4,
      "name": "Lost Bearings",
      "type": "coaster",
      "availability": "open",
      "status": "operating",
      "phase": "running",
      "manoeuvre": null,
      "operator": {
        "uuid": "b14e77d0-2c9a-4e6f-8d31-5f0c9a2e7b18",
        "name": "MrPopcorn"
      },
      "guestsInStation": 9,
      "trainInStation": false,
      "lapsToday": 318
    },
    {
      "id": 9,
      "name": "Temple Drop",
      "type": "coaster",
      "availability": "open",
      "status": "down",
      "phase": "idle",
      "manoeuvre": null,
      "operator": {
        "uuid": "e9d0c3a4-71f2-4b8e-a6c5-0d2f8b4e1c97",
        "name": "LunaRides"
      },
      "guestsInStation": 4,
      "trainInStation": true,
      "lapsToday": 96
    },
    {
      "id": 7,
      "name": "Wild Detour",
      "type": "flat",
      "availability": "maintenance",
      "status": "operating",
      "phase": "idle",
      "manoeuvre": null,
      "operator": null,
      "guestsInStation": 0,
      "trainInStation": true,
      "lapsToday": 141
    }
  ],
  "shows": [
    {
      "id": 3,
      "name": "Maya Night",
      "region": "maya-plaza",
      "location": "Maya Plaza",
      "music": {
        "title": "Maya Night",
        "author": "Wonder Park Orchestra"
      },
      "duration": 312.5,
      "running": true,
      "startedAt": "2026-10-11T20:15:00.000Z",
      "position": 14.2,
      "schedule": [
        {
          "days": [
            "monday",
            "tuesday",
            "wednesday",
            "thursday",
            "friday"
          ],
          "time": "19:00",
          "timeZone": "Europe/Paris",
          "from": null,
          "until": null,
          "enabled": true
        },
        {
          "days": [
            "saturday",
            "sunday"
          ],
          "time": "21:00",
          "timeZone": "Europe/Paris",
          "from": null,
          "until": null,
          "enabled": true
        }
      ],
      "nextRunAt": "2026-10-12T17:00:00.000Z"
    }
  ],
  "updatedAt": "2026-10-11T20:15:14.200Z"
}

Rides

Your rides, and how each stands.

GET/api/v1/rides

Every ride, live

Sorted by name. A ride is closed while the Minecraft server is not connected.

Answer · 200

A list of Ride, each with:

FieldType
id

The ride's number, as /utp ride list shows it.

integer
namestring
typecoaster | flat
availability

Whether the ride takes guests, as its owner set it. open. closed, maintenance and broken all close it to guests — no guest boards, nobody takes the operator's post — and say why; under maintenance, staff allowed to test the ride may still ride it and run it from its post, and those laps are not counted. Kept while the Minecraft server is offline.

open | closed | maintenance | broken
status

operating: running as usual. down: stopped by an emergency stop, until somebody releases it. closed: the Minecraft server is not connected, or the ride is not set up in the world. Treat any other value as unknown: more may come.

operating | down | closed
phase

Null while the ride is closed.

idle | dispatching | running | unloading | transferor null
manoeuvre

The ride working itself, or null: restart brings its trains out of storage, respawn puts its one train back in place — a coaster's in its station, a flat ride's seats — and hand-back takes over from an operator who left.

restart | hand-back | respawnor null
operator

Who holds the operator's post, or null when the ride runs by itself.

Playeror null
operator.uuid

The player's Minecraft UUID.

string
operator.name

The name they were last seen with.

string
guestsInStation

Players standing in the station's region right now: the nearest thing to a queue. Null while closed.

integeror null
trainInStation

Null while closed.

booleanor null
lapsToday

Laps ridden today (UTC), every rider counted.

integer

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 429rate_limitedMore than 120 requests in a minute with this key.
  • 504server_timeoutThe Minecraft server is connected but did not answer within five seconds.
Request
curl https://connect.your-park.com/api/v1/rides \
  -H "Authorization: Bearer $UTP_API_KEY"
Answer · 200
[
  {
    "id": 4,
    "name": "Lost Bearings",
    "type": "coaster",
    "availability": "open",
    "status": "operating",
    "phase": "running",
    "manoeuvre": null,
    "operator": {
      "uuid": "b14e77d0-2c9a-4e6f-8d31-5f0c9a2e7b18",
      "name": "MrPopcorn"
    },
    "guestsInStation": 9,
    "trainInStation": false,
    "lapsToday": 318
  },
  {
    "id": 9,
    "name": "Temple Drop",
    "type": "coaster",
    "availability": "open",
    "status": "down",
    "phase": "idle",
    "manoeuvre": null,
    "operator": {
      "uuid": "e9d0c3a4-71f2-4b8e-a6c5-0d2f8b4e1c97",
      "name": "LunaRides"
    },
    "guestsInStation": 4,
    "trainInStation": true,
    "lapsToday": 96
  },
  {
    "id": 7,
    "name": "Wild Detour",
    "type": "flat",
    "availability": "maintenance",
    "status": "operating",
    "phase": "idle",
    "manoeuvre": null,
    "operator": null,
    "guestsInStation": 0,
    "trainInStation": true,
    "lapsToday": 141
  }
]
GET/api/v1/rides/{rideId}

One ride, live

Parameters

NameInType
rideIdrequired

The ride's number, as /utp ride list shows it.

pathinteger

Answer · 200

FieldType
id

The ride's number, as /utp ride list shows it.

integer
namestring
typecoaster | flat
availability

Whether the ride takes guests, as its owner set it. open. closed, maintenance and broken all close it to guests — no guest boards, nobody takes the operator's post — and say why; under maintenance, staff allowed to test the ride may still ride it and run it from its post, and those laps are not counted. Kept while the Minecraft server is offline.

open | closed | maintenance | broken
status

operating: running as usual. down: stopped by an emergency stop, until somebody releases it. closed: the Minecraft server is not connected, or the ride is not set up in the world. Treat any other value as unknown: more may come.

operating | down | closed
phase

Null while the ride is closed.

idle | dispatching | running | unloading | transferor null
manoeuvre

The ride working itself, or null: restart brings its trains out of storage, respawn puts its one train back in place — a coaster's in its station, a flat ride's seats — and hand-back takes over from an operator who left.

restart | hand-back | respawnor null
operator

Who holds the operator's post, or null when the ride runs by itself.

Playeror null
operator.uuid

The player's Minecraft UUID.

string
operator.name

The name they were last seen with.

string
guestsInStation

Players standing in the station's region right now: the nearest thing to a queue. Null while closed.

integeror null
trainInStation

Null while closed.

booleanor null
lapsToday

Laps ridden today (UTC), every rider counted.

integer

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 404not_foundNothing with that number or name.
  • 429rate_limitedMore than 120 requests in a minute with this key.
  • 504server_timeoutThe Minecraft server is connected but did not answer within five seconds.
Request
curl https://connect.your-park.com/api/v1/rides/4 \
  -H "Authorization: Bearer $UTP_API_KEY"
Answer · 200
{
  "id": 4,
  "name": "Lost Bearings",
  "type": "coaster",
  "availability": "open",
  "status": "operating",
  "phase": "running",
  "manoeuvre": null,
  "operator": {
    "uuid": "b14e77d0-2c9a-4e6f-8d31-5f0c9a2e7b18",
    "name": "MrPopcorn"
  },
  "guestsInStation": 9,
  "trainInStation": false,
  "lapsToday": 318
}
POST/api/v1/rides/{rideId}/availabilityRead and write key

Open or close a ride

What /utp ride availability <id> <state> does in game. Closed — closed, maintenance or broken, which say why — no guest boards, guests already seated are let off, the operator leaves the post and nobody may take it; trains out on their lap come back and let their riders off. Under maintenance, staff allowed to test the ride may take the post again, ride it and send it round; those laps are not counted. The ride's commands for opening and closing run. Kept by the park, so it holds through a restart, and applied as soon as the Minecraft server is connected. Setting what the ride already is changes nothing and sends no webhook.

Parameters

NameInType
rideIdrequired

The ride's number, as /utp ride list shows it.

pathinteger

Body

FieldType
availability

Whether the ride takes guests, as its owner set it. open. closed, maintenance and broken all close it to guests — no guest boards, nobody takes the operator's post — and say why; under maintenance, staff allowed to test the ride may still ride it and run it from its post, and those laps are not counted. Kept while the Minecraft server is offline.

open | closed | maintenance | broken

Answer · 200

FieldType
id

The ride's number, as /utp ride list shows it.

integer
namestring
typecoaster | flat
availability

Whether the ride takes guests, as its owner set it. open. closed, maintenance and broken all close it to guests — no guest boards, nobody takes the operator's post — and say why; under maintenance, staff allowed to test the ride may still ride it and run it from its post, and those laps are not counted. Kept while the Minecraft server is offline.

open | closed | maintenance | broken
status

operating: running as usual. down: stopped by an emergency stop, until somebody releases it. closed: the Minecraft server is not connected, or the ride is not set up in the world. Treat any other value as unknown: more may come.

operating | down | closed
phase

Null while the ride is closed.

idle | dispatching | running | unloading | transferor null
manoeuvre

The ride working itself, or null: restart brings its trains out of storage, respawn puts its one train back in place — a coaster's in its station, a flat ride's seats — and hand-back takes over from an operator who left.

restart | hand-back | respawnor null
operator

Who holds the operator's post, or null when the ride runs by itself.

Playeror null
operator.uuid

The player's Minecraft UUID.

string
operator.name

The name they were last seen with.

string
guestsInStation

Players standing in the station's region right now: the nearest thing to a queue. Null while closed.

integeror null
trainInStation

Null while closed.

booleanor null
lapsToday

Laps ridden today (UTC), every rider counted.

integer

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 403forbiddenThe key can only read.
  • 404not_foundNothing with that number or name.
  • 422invalidA parameter or a field is not right; the message says which.
  • 429rate_limitedMore than 120 requests in a minute with this key.
  • 504server_timeoutThe Minecraft server is connected but did not answer within five seconds.
Request
curl -X POST https://connect.your-park.com/api/v1/rides/4/availability \
  -H "Authorization: Bearer $UTP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"availability":"maintenance"}'
Answer · 200
{
  "id": 4,
  "name": "Lost Bearings",
  "type": "coaster",
  "availability": "maintenance",
  "status": "operating",
  "phase": "running",
  "manoeuvre": null,
  "operator": {
    "uuid": "b14e77d0-2c9a-4e6f-8d31-5f0c9a2e7b18",
    "name": "MrPopcorn"
  },
  "guestsInStation": 9,
  "trainInStation": false,
  "lapsToday": 318
}

Shows

The sequences of Administration → Sequences.

GET/api/v1/shows

Every show

The sequences of Administration → Sequences, whether each is running, and when each next starts on its schedule.

Answer · 200

A list of Show, each with:

FieldType
id

The show's number, as /utp sequence list shows it.

integer
namestring
region

The WorldGuard region its music is heard in, or null.

stringor null
location

Where guests find it, as the owner wrote it: Main Lake. Null for a show kept off the guests' schedule.

stringor null
music

The track it plays to, or null for a show without music.

objector null
music.titlestring
music.authorstringor null
duration

In seconds: the music's length or the last cue, whichever ends later.

numberor null
runningboolean
startedAt

When the run under way started, or null.

date-timeor null
position

Seconds into the show, while it runs.

numberor null
schedule

When it starts by itself, as set in Administration → Sequences. Empty for a show started by hand.

ShowSchedule[]
schedule[].days

The days of the week it starts on.

monday | tuesday | wednesday | thursday | friday | saturday | sunday[]
schedule[].time

HH:MM, on the park's clocks: 19:00 in timeZone.

string
schedule[].timeZone

The park's time zone, by its IANA name: Europe/Paris.

string
schedule[].from

The first day it runs, or null.

stringor null
schedule[].until

The last day it runs, or null. The same as from for a single day.

stringor null
schedule[].enabled

False for a time switched off in the administration: kept, but it starts nothing.

boolean
nextRunAt

When it next starts on its schedule, or null. It starts only if somebody is online then.

date-timeor null

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 429rate_limitedMore than 120 requests in a minute with this key.
Request
curl https://connect.your-park.com/api/v1/shows \
  -H "Authorization: Bearer $UTP_API_KEY"
Answer · 200
[
  {
    "id": 3,
    "name": "Maya Night",
    "region": "maya-plaza",
    "location": "Maya Plaza",
    "music": {
      "title": "Maya Night",
      "author": "Wonder Park Orchestra"
    },
    "duration": 312.5,
    "running": true,
    "startedAt": "2026-10-11T20:15:00.000Z",
    "position": 14.2,
    "schedule": [
      {
        "days": [
          "monday",
          "tuesday",
          "wednesday",
          "thursday",
          "friday"
        ],
        "time": "19:00",
        "timeZone": "Europe/Paris",
        "from": null,
        "until": null,
        "enabled": true
      },
      {
        "days": [
          "saturday",
          "sunday"
        ],
        "time": "21:00",
        "timeZone": "Europe/Paris",
        "from": null,
        "until": null,
        "enabled": true
      }
    ],
    "nextRunAt": "2026-10-12T17:00:00.000Z"
  }
]
POST/api/v1/shows/{showId}/startRead and write key

Start a show

What /utp sequence start <id> [from] does in the console: the commands run, and the guests in the show's region hear its music.

Parameters

NameInType
showIdrequired

The show's number, as /utp sequence list shows it.

pathinteger

Body

FieldType
from

Where to start: seconds (90) or minutes and seconds ("1:30", "1:30.250"). The beginning if left out.

number | string

Answer · 200

FieldType
id

The show's number, as /utp sequence list shows it.

integer
namestring
region

The WorldGuard region its music is heard in, or null.

stringor null
location

Where guests find it, as the owner wrote it: Main Lake. Null for a show kept off the guests' schedule.

stringor null
music

The track it plays to, or null for a show without music.

objector null
music.titlestring
music.authorstringor null
duration

In seconds: the music's length or the last cue, whichever ends later.

numberor null
runningboolean
startedAt

When the run under way started, or null.

date-timeor null
position

Seconds into the show, while it runs.

numberor null
schedule

When it starts by itself, as set in Administration → Sequences. Empty for a show started by hand.

ShowSchedule[]
schedule[].days

The days of the week it starts on.

monday | tuesday | wednesday | thursday | friday | saturday | sunday[]
schedule[].time

HH:MM, on the park's clocks: 19:00 in timeZone.

string
schedule[].timeZone

The park's time zone, by its IANA name: Europe/Paris.

string
schedule[].from

The first day it runs, or null.

stringor null
schedule[].until

The last day it runs, or null. The same as from for a single day.

stringor null
schedule[].enabled

False for a time switched off in the administration: kept, but it starts nothing.

boolean
nextRunAt

When it next starts on its schedule, or null. It starts only if somebody is online then.

date-timeor null

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 403forbiddenThe key can only read.
  • 404not_foundNothing with that number or name.
  • 409already_runningIt is already running: stop it first.
  • 422invalidA parameter or a field is not right; the message says which.
  • 429rate_limitedMore than 120 requests in a minute with this key.
  • 503server_offlineThe Minecraft server is not connected, so nothing can be done in the game.
  • 504server_timeoutThe Minecraft server is connected but did not answer within five seconds.
Request
curl -X POST https://connect.your-park.com/api/v1/shows/3/start \
  -H "Authorization: Bearer $UTP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from":"1:30"}'
Answer · 200
{
  "id": 3,
  "name": "Maya Night",
  "region": "maya-plaza",
  "location": "Maya Plaza",
  "music": {
    "title": "Maya Night",
    "author": "Wonder Park Orchestra"
  },
  "duration": 312.5,
  "running": true,
  "startedAt": "2026-10-11T20:15:00.000Z",
  "position": 90,
  "schedule": [
    {
      "days": [
        "monday",
        "tuesday",
        "wednesday",
        "thursday",
        "friday"
      ],
      "time": "19:00",
      "timeZone": "Europe/Paris",
      "from": null,
      "until": null,
      "enabled": true
    },
    {
      "days": [
        "saturday",
        "sunday"
      ],
      "time": "21:00",
      "timeZone": "Europe/Paris",
      "from": null,
      "until": null,
      "enabled": true
    }
  ],
  "nextRunAt": "2026-10-12T17:00:00.000Z"
}
POST/api/v1/shows/{showId}/stopRead and write key

Stop a show

What /utp sequence stop <id> does. Stopping a show that is not running is not an error.

Parameters

NameInType
showIdrequired

The show's number, as /utp sequence list shows it.

pathinteger

Answer · 200

FieldType
id

The show's number, as /utp sequence list shows it.

integer
namestring
region

The WorldGuard region its music is heard in, or null.

stringor null
location

Where guests find it, as the owner wrote it: Main Lake. Null for a show kept off the guests' schedule.

stringor null
music

The track it plays to, or null for a show without music.

objector null
music.titlestring
music.authorstringor null
duration

In seconds: the music's length or the last cue, whichever ends later.

numberor null
runningboolean
startedAt

When the run under way started, or null.

date-timeor null
position

Seconds into the show, while it runs.

numberor null
schedule

When it starts by itself, as set in Administration → Sequences. Empty for a show started by hand.

ShowSchedule[]
schedule[].days

The days of the week it starts on.

monday | tuesday | wednesday | thursday | friday | saturday | sunday[]
schedule[].time

HH:MM, on the park's clocks: 19:00 in timeZone.

string
schedule[].timeZone

The park's time zone, by its IANA name: Europe/Paris.

string
schedule[].from

The first day it runs, or null.

stringor null
schedule[].until

The last day it runs, or null. The same as from for a single day.

stringor null
schedule[].enabled

False for a time switched off in the administration: kept, but it starts nothing.

boolean
nextRunAt

When it next starts on its schedule, or null. It starts only if somebody is online then.

date-timeor null

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 403forbiddenThe key can only read.
  • 404not_foundNothing with that number or name.
  • 429rate_limitedMore than 120 requests in a minute with this key.
  • 503server_offlineThe Minecraft server is not connected, so nothing can be done in the game.
  • 504server_timeoutThe Minecraft server is connected but did not answer within five seconds.
Request
curl -X POST https://connect.your-park.com/api/v1/shows/3/stop \
  -H "Authorization: Bearer $UTP_API_KEY"
Answer · 200
{
  "id": 3,
  "name": "Maya Night",
  "region": "maya-plaza",
  "location": "Maya Plaza",
  "music": {
    "title": "Maya Night",
    "author": "Wonder Park Orchestra"
  },
  "duration": 312.5,
  "running": false,
  "startedAt": null,
  "position": null,
  "schedule": [
    {
      "days": [
        "monday",
        "tuesday",
        "wednesday",
        "thursday",
        "friday"
      ],
      "time": "19:00",
      "timeZone": "Europe/Paris",
      "from": null,
      "until": null,
      "enabled": true
    },
    {
      "days": [
        "saturday",
        "sunday"
      ],
      "time": "21:00",
      "timeZone": "Europe/Paris",
      "from": null,
      "until": null,
      "enabled": true
    }
  ],
  "nextRunAt": "2026-10-12T17:00:00.000Z"
}

Stats

Laps, counted as the train stops back in the station.

GET/api/v1/stats/rides

Laps per ride

The most ridden first. A ride since removed keeps the laps ridden on it.

Parameters

NameInType
period

day is today, week the last seven days and month the last thirty, today included — days in UTC.

queryday | week | month | all

Answer · 200

A list of RideStats, each with:

FieldType
rideIdinteger
name

The ride's name, or Ride #n for a ride since removed.

string
lapsinteger
riders

Different players.

integer

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 422invalidA parameter or a field is not right; the message says which.
  • 429rate_limitedMore than 120 requests in a minute with this key.
Request
curl https://connect.your-park.com/api/v1/stats/rides \
  -H "Authorization: Bearer $UTP_API_KEY"
Answer · 200
[
  {
    "rideId": 4,
    "name": "Lost Bearings",
    "laps": 318,
    "riders": 187
  },
  {
    "rideId": 7,
    "name": "Wild Detour",
    "laps": 141,
    "riders": 102
  }
]
GET/api/v1/stats/top-riders

Top riders

The players with the most laps, on one ride or on the whole park — the list a leaderboard in the park shows.

Parameters

NameInType
rideId

One ride's riders. The whole park if left out.

queryinteger
period

day is today, week the last seven days and month the last thirty, today included — days in UTC.

queryday | week | month | all
limitqueryinteger

Answer · 200

A list of TopRider, each with:

FieldType
rank

First is 1. Players level on laps share a rank.

integer
playerPlayer
player.uuid

The player's Minecraft UUID.

string
player.name

The name they were last seen with.

string
lapsinteger

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 422invalidA parameter or a field is not right; the message says which.
  • 429rate_limitedMore than 120 requests in a minute with this key.
Request
curl https://connect.your-park.com/api/v1/stats/top-riders \
  -H "Authorization: Bearer $UTP_API_KEY"
Answer · 200
[
  {
    "rank": 1,
    "player": {
      "uuid": "3f8a2c1e-6b47-4d1a-9c2e-1a7b5e0d9f42",
      "name": "Kaelith"
    },
    "laps": 64
  },
  {
    "rank": 2,
    "player": {
      "uuid": "e9d0c3a4-71f2-4b8e-a6c5-0d2f8b4e1c97",
      "name": "LunaRides"
    },
    "laps": 51
  }
]
GET/api/v1/players/{player}

A player's laps

Parameters

NameInType
playerrequired

A UUID, or the name the player was last seen with.

pathstring

Answer · 200

FieldType
uuidstring
namestring
lastSeen

When the server last reported them online.

date-timeor null
listening

Their audio page is open right now.

boolean
totalLapsinteger
ridesobject[]
rides[].rideIdinteger
rides[].namestring
rides[].lapsinteger
rides[].rank

Where they stand on that ride's leaderboard.

integer

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 404not_foundNothing with that number or name.
  • 429rate_limitedMore than 120 requests in a minute with this key.
Request
curl https://connect.your-park.com/api/v1/players/Kaelith \
  -H "Authorization: Bearer $UTP_API_KEY"
Answer · 200
{
  "uuid": "3f8a2c1e-6b47-4d1a-9c2e-1a7b5e0d9f42",
  "name": "Kaelith",
  "lastSeen": "2026-10-11T19:52:08.000Z",
  "listening": true,
  "totalLaps": 76,
  "rides": [
    {
      "rideId": 4,
      "name": "Lost Bearings",
      "laps": 64,
      "rank": 1
    },
    {
      "rideId": 7,
      "name": "Wild Detour",
      "laps": 12,
      "rank": 8
    }
  ]
}

Photos

On-ride photos, and the pages their QR codes open.

GET/api/v1/photos

The latest on-ride photos

Photos still within the retention set in the administration, newest first.

Parameters

NameInType
rideId

One ride's photos.

queryinteger
since

Only photos taken after this instant. Pass the last takenAt you saw to get only new ones.

querydate-time
limitqueryinteger

Answer · 200

A list of Photo, each with:

FieldType
idstring
rideIdinteger
rideNamestring
takenAt

An instant, in ISO 8601, UTC.

date-time
ridersPlayer[]
riders[].uuid

The player's Minecraft UUID.

string
riders[].name

The name they were last seen with.

string
pageUrl

The page the photo wall's QR code opens. Anyone with the link sees it, until the photo expires.

stringor null
frames

The pictures, one per group of rows, as images anyone with the link can load.

string[]

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 422invalidA parameter or a field is not right; the message says which.
  • 429rate_limitedMore than 120 requests in a minute with this key.
Request
curl https://connect.your-park.com/api/v1/photos \
  -H "Authorization: Bearer $UTP_API_KEY"
Answer · 200
[
  {
    "id": "01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
    "rideId": 4,
    "rideName": "Lost Bearings",
    "takenAt": "2026-10-11T20:11:47.000Z",
    "riders": [
      {
        "uuid": "3f8a2c1e-6b47-4d1a-9c2e-1a7b5e0d9f42",
        "name": "Kaelith"
      },
      {
        "uuid": "b14e77d0-2c9a-4e6f-8d31-5f0c9a2e7b18",
        "name": "MrPopcorn"
      }
    ],
    "pageUrl": "https://connect.your-park.com/p/9F2A71C0DE",
    "frames": [
      "https://connect.your-park.com/api/p/9F2A71C0DE/0",
      "https://connect.your-park.com/api/p/9F2A71C0DE/1"
    ]
  }
]

Audio

Tracks on a player's phone.

POST/api/v1/audio/playRead and write key

Play a track for a player

What /utp audio send does: the track plays once on the player's phone. Nothing plays if their page is not open — listening says so. Their region's music comes back when they next cross a border.

Body

FieldType
player

A player's name or UUID.

string
url

An MP3, OGG or M4A file anyone can download.

string
volume

From 0 to 100. Left as it is if not given.

integer

Answer · 200

FieldType
playerPlayer
player.uuid

The player's Minecraft UUID.

string
player.name

The name they were last seen with.

string
listening

Whether their page is open. If it is not, nothing plays.

boolean

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 403forbiddenThe key can only read.
  • 404not_onlineThe player is not on the server.
  • 422invalidA parameter or a field is not right; the message says which.
  • 429rate_limitedMore than 120 requests in a minute with this key.
  • 503server_offlineThe Minecraft server is not connected, so nothing can be done in the game.
  • 504server_timeoutThe Minecraft server is connected but did not answer within five seconds.
Request
curl -X POST https://connect.your-park.com/api/v1/audio/play \
  -H "Authorization: Bearer $UTP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"player":"Kaelith","url":"https://music.your-park.com/welcome.mp3","volume":80}'
Answer · 200
{
  "player": {
    "uuid": "3f8a2c1e-6b47-4d1a-9c2e-1a7b5e0d9f42",
    "name": "Kaelith"
  },
  "listening": true
}
POST/api/v1/audio/stopRead and write key

Stop the audio of a player

What /utp audio stop does.

Body

FieldType
player

A player's name or UUID.

string

Answer · 200

FieldType
playerPlayer
player.uuid

The player's Minecraft UUID.

string
player.name

The name they were last seen with.

string
listening

Whether their page is open. If it is not, nothing plays.

boolean

Errors

  • 401unauthorizedNo key, or a key that does not exist (any more).
  • 403forbiddenThe key can only read.
  • 404not_onlineThe player is not on the server.
  • 422invalidA parameter or a field is not right; the message says which.
  • 429rate_limitedMore than 120 requests in a minute with this key.
  • 503server_offlineThe Minecraft server is not connected, so nothing can be done in the game.
  • 504server_timeoutThe Minecraft server is connected but did not answer within five seconds.
Request
curl -X POST https://connect.your-park.com/api/v1/audio/stop \
  -H "Authorization: Bearer $UTP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"player":"Kaelith"}'
Answer · 200
{
  "player": {
    "uuid": "3f8a2c1e-6b47-4d1a-9c2e-1a7b5e0d9f42",
    "name": "Kaelith"
  },
  "listening": true
}

Webhooks

Rather than asking, be told: add a webhook in Administration → Settings → API, pick its events, and your address receives a POST as each one happens. Answer with any 2xx; anything else is tried again, four more times over the next hour.

Every delivery is signed as the Standard Webhooks specification says, with the secret shown when you added the webhook: its libraries check it for you, or a few lines do.

Headers

NameInType
webhook-idrequired

The message's identifier, the same on every retry: use it to ignore a message you already have.

headerstring
webhook-timestamprequired

When this attempt was sent, in Unix seconds. Refuse a message more than five minutes old.

headerinteger
webhook-signaturerequired

v1, and the HMAC-SHA256, in base64, of webhook-id.webhook-timestamp.body with your webhook's secret — the part after whsec_, decoded from base64.

headerstring
Checking a delivery · Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

/** True if this delivery was signed with your webhook's secret. */
function isGenuine(secret, headers, body) {
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const signed = `${headers["webhook-id"]}.${headers["webhook-timestamp"]}.${body}`;
  const expected = "v1," + createHmac("sha256", key).update(signed).digest("base64");
  const age = Date.now() / 1000 - Number(headers["webhook-timestamp"]);
  return age < 300 && headers["webhook-signature"].split(" ").some((given) =>
    given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected)));
}
ride.lap.completed

A lap is over

Sent as the train stops back in the station — or at the ride's unload platform — before anybody is let out: one message per lap, with everybody aboard.

data

FieldType
rideIdinteger
rideNamestring
riders

Everybody aboard for the lap.

Player[]
riders[].uuid

The player's Minecraft UUID.

string
riders[].name

The name they were last seen with.

string
Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "ride.lap.completed",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {
    "rideId": 4,
    "rideName": "Lost Bearings",
    "riders": [
      {
        "uuid": "3f8a2c1e-6b47-4d1a-9c2e-1a7b5e0d9f42",
        "name": "Kaelith"
      },
      {
        "uuid": "b14e77d0-2c9a-4e6f-8d31-5f0c9a2e7b18",
        "name": "MrPopcorn"
      }
    ]
  }
}
ride.photo.taken

An on-ride photo was taken

Sent once the lap is stored, a few seconds after the shutter. The links work at once: the pictures are drawn when first opened.

data

FieldType
idstring
rideIdinteger
rideNamestring
takenAt

An instant, in ISO 8601, UTC.

date-time
ridersPlayer[]
riders[].uuid

The player's Minecraft UUID.

string
riders[].name

The name they were last seen with.

string
pageUrl

The page the photo wall's QR code opens. Anyone with the link sees it, until the photo expires.

stringor null
frames

The pictures, one per group of rows, as images anyone with the link can load.

string[]
Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "ride.photo.taken",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {
    "id": "01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
    "rideId": 4,
    "rideName": "Lost Bearings",
    "takenAt": "2026-10-11T20:11:47.000Z",
    "riders": [
      {
        "uuid": "3f8a2c1e-6b47-4d1a-9c2e-1a7b5e0d9f42",
        "name": "Kaelith"
      },
      {
        "uuid": "b14e77d0-2c9a-4e6f-8d31-5f0c9a2e7b18",
        "name": "MrPopcorn"
      }
    ],
    "pageUrl": "https://connect.your-park.com/p/9F2A71C0DE",
    "frames": [
      "https://connect.your-park.com/api/p/9F2A71C0DE/0",
      "https://connect.your-park.com/api/p/9F2A71C0DE/1"
    ]
  }
}
ride.stopped

A ride is stopped

An emergency stop: pressed by an operator, or engaged by the ride itself when a transfer manoeuvre did not take effect.

data

FieldType
rideIdinteger
rideNamestring
by

The operator who pressed it, or null when the ride stopped itself.

Playeror null
by.uuid

The player's Minecraft UUID.

string
by.name

The name they were last seen with.

string
Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "ride.stopped",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {
    "rideId": 9,
    "rideName": "Temple Drop",
    "by": {
      "uuid": "e9d0c3a4-71f2-4b8e-a6c5-0d2f8b4e1c97",
      "name": "LunaRides"
    }
  }
}
ride.resumed

A stopped ride runs again

The emergency stop was released.

data

FieldType
rideIdinteger
rideNamestring
by

The operator who released it, or null.

Playeror null
by.uuid

The player's Minecraft UUID.

string
by.name

The name they were last seen with.

string
Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "ride.resumed",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {
    "rideId": 9,
    "rideName": "Temple Drop",
    "by": {
      "uuid": "e9d0c3a4-71f2-4b8e-a6c5-0d2f8b4e1c97",
      "name": "LunaRides"
    }
  }
}
ride.availability.changed

A ride opens or closes

Its availability was changed: in game with /utp ride availability, in the administration, or through this API. Not sent when it was set to what it already was.

data

FieldType
rideIdinteger
rideNamestring
availability

Whether the ride takes guests, as its owner set it. open. closed, maintenance and broken all close it to guests — no guest boards, nobody takes the operator's post — and say why; under maintenance, staff allowed to test the ride may still ride it and run it from its post, and those laps are not counted. Kept while the Minecraft server is offline.

open | closed | maintenance | broken
previous

How it stood before.

open | closed | maintenance | broken
by

The player who changed it, in game or in the administration; null from the server's console or through this API.

Playeror null
by.uuid

The player's Minecraft UUID.

string
by.name

The name they were last seen with.

string
Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "ride.availability.changed",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {
    "rideId": 7,
    "rideName": "Wild Detour",
    "availability": "maintenance",
    "previous": "open",
    "by": {
      "uuid": "e9d0c3a4-71f2-4b8e-a6c5-0d2f8b4e1c97",
      "name": "LunaRides"
    }
  }
}
show.upcoming

A show starts in thirty minutes

Sent thirty minutes before a show starts on its schedule — or as soon as the Minecraft server connects, if it came back later than that. Not sent while the server is offline. The show itself starts only if somebody is online at the time: see show.skipped.

data

FieldType
showIdinteger
showNamestring
showLocation

Where guests find it, or null.

stringor null
startsAt

When it starts, on its schedule.

date-time
Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "show.upcoming",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {
    "showId": 3,
    "showName": "Maya Night",
    "showLocation": "Maya Plaza",
    "startsAt": "2026-10-12T17:00:00.000Z"
  }
}
show.started

A show started

Sent when a show starts from the beginning on its schedule, from the administration, or through this API. A show started in game — a console command, a ride's cue — is not reported, nor one started part-way through, which is a rehearsal.

data

FieldType
showIdinteger
showNamestring
showLocation

Where guests find it, or null.

stringor null
trigger

schedule: on its schedule. admin: from the administration. api: through this API.

schedule | admin | api
scheduledFor

The time it was scheduled for, when trigger is schedule.

date-timeor null
Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "show.started",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {
    "showId": 3,
    "showName": "Maya Night",
    "showLocation": "Maya Plaza",
    "trigger": "schedule",
    "scheduledFor": "2026-10-12T17:00:00.000Z"
  }
}
show.skipped

A scheduled show did not start

Sent when a show's scheduled time comes and it does not start: nobody was online, it was still running, or the Minecraft server was not there.

data

FieldType
showIdinteger
showNamestring
showLocation

Where guests find it, or null.

stringor null
scheduledFor

The time it was scheduled for.

date-time
reason

nobody_online: nobody was on the Minecraft server. already_running: it was still running from an earlier start. server_offline: the Minecraft server was not connected. not_ready: the server did not have the show yet. no_answer: the server did not answer.

nobody_online | already_running | server_offline | not_ready | no_answer
Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "show.skipped",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {
    "showId": 3,
    "showName": "Maya Night",
    "showLocation": "Maya Plaza",
    "scheduledFor": "2026-10-12T17:00:00.000Z",
    "reason": "nobody_online"
  }
}
server.online

The Minecraft server is connected

Sent when the server comes back after server.offline, and when the instance itself has restarted.

Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "server.online",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {}
}
server.offline

The Minecraft server is gone

Sent when the server stops, or when nothing has been heard from it for twenty seconds.

data

FieldType
reason

stopping: the server shut down cleanly. lost: nothing heard from it for twenty seconds.

stopping | lost
Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "server.offline",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {
    "reason": "stopping"
  }
}
webhook.test

A test

Sent by the Send a test button in the administration, to this webhook alone.

data

FieldType
messagestring
Body
{
  "id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
  "type": "webhook.test",
  "timestamp": "2026-10-11T20:11:49.000Z",
  "data": {
    "message": "This is a test from your park's administration."
  }
}

Get Ultimate Theme Park

Secure payment by Stripe. You will receive an invoice by e-mail.