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.
Park
Everything live, in one call.
/api/v1/park/liveThe 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
| Field | Type |
|---|---|
park | object |
park.nameYour park's name, from the administration. | string |
park.serverOnlineWhether the Minecraft server is connected. | boolean |
park.guestsListeningPlayers with their audio page open. | integer |
park.timeZoneThe park's time zone, which the shows' schedules are written in. Null until a show is scheduled. | stringor null |
rides | Ride[] |
rides[].idThe ride's number, as | integer |
rides[].name | string |
rides[].type | coaster | flat |
rides[].availabilityWhether the ride takes guests, as its owner set it. | open | closed | maintenance | broken |
rides[].status
| operating | down | closed |
rides[].phaseNull while the ride is | idle | dispatching | running | unloading | transferor null |
rides[].manoeuvreThe ride working itself, or null: | restart | hand-back | respawnor null |
rides[].operatorWho holds the operator's post, or null when the ride runs by itself. | Playeror null |
rides[].operator.uuidThe player's Minecraft UUID. | string |
rides[].operator.nameThe name they were last seen with. | string |
rides[].guestsInStationPlayers standing in the station's region right now: the nearest thing to a queue. Null while | integeror null |
rides[].trainInStationNull while | booleanor null |
rides[].lapsTodayLaps ridden today (UTC), every rider counted. | integer |
shows | Show[] |
shows[].idThe show's number, as | integer |
shows[].name | string |
shows[].regionThe WorldGuard region its music is heard in, or null. | stringor null |
shows[].locationWhere guests find it, as the owner wrote it: | stringor null |
shows[].musicThe track it plays to, or null for a show without music. | objector null |
shows[].music.title | string |
shows[].music.author | stringor null |
shows[].durationIn seconds: the music's length or the last cue, whichever ends later. | numberor null |
shows[].running | boolean |
shows[].startedAtWhen the run under way started, or null. | date-timeor null |
shows[].positionSeconds into the show, while it runs. | numberor null |
shows[].scheduleWhen it starts by itself, as set in Administration → Sequences. Empty for a show started by hand. | ShowSchedule[] |
shows[].schedule[].daysThe days of the week it starts on. | monday | tuesday | wednesday | thursday | friday | saturday | sunday[] |
shows[].schedule[].time
| string |
shows[].schedule[].timeZoneThe park's time zone, by its IANA name: | string |
shows[].schedule[].fromThe first day it runs, or null. | stringor null |
shows[].schedule[].untilThe last day it runs, or null. The same as | stringor null |
shows[].schedule[].enabledFalse for a time switched off in the administration: kept, but it starts nothing. | boolean |
shows[].nextRunAtWhen it next starts on its schedule, or null. It starts only if somebody is online then. | date-timeor null |
updatedAtAn 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.
curl https://connect.your-park.com/api/v1/park/live \ -H "Authorization: Bearer $UTP_API_KEY"
{
"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.
/api/v1/ridesEvery ride, live
Sorted by name. A ride is closed while the Minecraft server is not connected.
Answer · 200
A list of Ride, each with:
| Field | Type |
|---|---|
idThe ride's number, as | integer |
name | string |
type | coaster | flat |
availabilityWhether the ride takes guests, as its owner set it. | open | closed | maintenance | broken |
status
| operating | down | closed |
phaseNull while the ride is | idle | dispatching | running | unloading | transferor null |
manoeuvreThe ride working itself, or null: | restart | hand-back | respawnor null |
operatorWho holds the operator's post, or null when the ride runs by itself. | Playeror null |
operator.uuidThe player's Minecraft UUID. | string |
operator.nameThe name they were last seen with. | string |
guestsInStationPlayers standing in the station's region right now: the nearest thing to a queue. Null while | integeror null |
trainInStationNull while | booleanor null |
lapsTodayLaps 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.
curl https://connect.your-park.com/api/v1/rides \ -H "Authorization: Bearer $UTP_API_KEY"
[
{
"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
}
]/api/v1/rides/{rideId}One ride, live
Parameters
| Name | In | Type |
|---|---|---|
rideIdrequiredThe ride's number, as | path | integer |
Answer · 200
| Field | Type |
|---|---|
idThe ride's number, as | integer |
name | string |
type | coaster | flat |
availabilityWhether the ride takes guests, as its owner set it. | open | closed | maintenance | broken |
status
| operating | down | closed |
phaseNull while the ride is | idle | dispatching | running | unloading | transferor null |
manoeuvreThe ride working itself, or null: | restart | hand-back | respawnor null |
operatorWho holds the operator's post, or null when the ride runs by itself. | Playeror null |
operator.uuidThe player's Minecraft UUID. | string |
operator.nameThe name they were last seen with. | string |
guestsInStationPlayers standing in the station's region right now: the nearest thing to a queue. Null while | integeror null |
trainInStationNull while | booleanor null |
lapsTodayLaps 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.
curl https://connect.your-park.com/api/v1/rides/4 \ -H "Authorization: Bearer $UTP_API_KEY"
{
"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
}/api/v1/rides/{rideId}/availabilityRead and write keyOpen 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
| Name | In | Type |
|---|---|---|
rideIdrequiredThe ride's number, as | path | integer |
Body
| Field | Type |
|---|---|
availabilityWhether the ride takes guests, as its owner set it. | open | closed | maintenance | broken |
Answer · 200
| Field | Type |
|---|---|
idThe ride's number, as | integer |
name | string |
type | coaster | flat |
availabilityWhether the ride takes guests, as its owner set it. | open | closed | maintenance | broken |
status
| operating | down | closed |
phaseNull while the ride is | idle | dispatching | running | unloading | transferor null |
manoeuvreThe ride working itself, or null: | restart | hand-back | respawnor null |
operatorWho holds the operator's post, or null when the ride runs by itself. | Playeror null |
operator.uuidThe player's Minecraft UUID. | string |
operator.nameThe name they were last seen with. | string |
guestsInStationPlayers standing in the station's region right now: the nearest thing to a queue. Null while | integeror null |
trainInStationNull while | booleanor null |
lapsTodayLaps 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.
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"}'{
"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.
/api/v1/showsEvery 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:
| Field | Type |
|---|---|
idThe show's number, as | integer |
name | string |
regionThe WorldGuard region its music is heard in, or null. | stringor null |
locationWhere guests find it, as the owner wrote it: | stringor null |
musicThe track it plays to, or null for a show without music. | objector null |
music.title | string |
music.author | stringor null |
durationIn seconds: the music's length or the last cue, whichever ends later. | numberor null |
running | boolean |
startedAtWhen the run under way started, or null. | date-timeor null |
positionSeconds into the show, while it runs. | numberor null |
scheduleWhen it starts by itself, as set in Administration → Sequences. Empty for a show started by hand. | ShowSchedule[] |
schedule[].daysThe days of the week it starts on. | monday | tuesday | wednesday | thursday | friday | saturday | sunday[] |
schedule[].time
| string |
schedule[].timeZoneThe park's time zone, by its IANA name: | string |
schedule[].fromThe first day it runs, or null. | stringor null |
schedule[].untilThe last day it runs, or null. The same as | stringor null |
schedule[].enabledFalse for a time switched off in the administration: kept, but it starts nothing. | boolean |
nextRunAtWhen 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.
curl https://connect.your-park.com/api/v1/shows \ -H "Authorization: Bearer $UTP_API_KEY"
[
{
"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"
}
]/api/v1/shows/{showId}/startRead and write keyStart 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
| Name | In | Type |
|---|---|---|
showIdrequiredThe show's number, as | path | integer |
Body
| Field | Type |
|---|---|
fromWhere to start: seconds ( | number | string |
Answer · 200
| Field | Type |
|---|---|
idThe show's number, as | integer |
name | string |
regionThe WorldGuard region its music is heard in, or null. | stringor null |
locationWhere guests find it, as the owner wrote it: | stringor null |
musicThe track it plays to, or null for a show without music. | objector null |
music.title | string |
music.author | stringor null |
durationIn seconds: the music's length or the last cue, whichever ends later. | numberor null |
running | boolean |
startedAtWhen the run under way started, or null. | date-timeor null |
positionSeconds into the show, while it runs. | numberor null |
scheduleWhen it starts by itself, as set in Administration → Sequences. Empty for a show started by hand. | ShowSchedule[] |
schedule[].daysThe days of the week it starts on. | monday | tuesday | wednesday | thursday | friday | saturday | sunday[] |
schedule[].time
| string |
schedule[].timeZoneThe park's time zone, by its IANA name: | string |
schedule[].fromThe first day it runs, or null. | stringor null |
schedule[].untilThe last day it runs, or null. The same as | stringor null |
schedule[].enabledFalse for a time switched off in the administration: kept, but it starts nothing. | boolean |
nextRunAtWhen 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.
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"}'{
"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"
}/api/v1/shows/{showId}/stopRead and write keyStop a show
What /utp sequence stop <id> does. Stopping a show that is not running is not an error.
Parameters
| Name | In | Type |
|---|---|---|
showIdrequiredThe show's number, as | path | integer |
Answer · 200
| Field | Type |
|---|---|
idThe show's number, as | integer |
name | string |
regionThe WorldGuard region its music is heard in, or null. | stringor null |
locationWhere guests find it, as the owner wrote it: | stringor null |
musicThe track it plays to, or null for a show without music. | objector null |
music.title | string |
music.author | stringor null |
durationIn seconds: the music's length or the last cue, whichever ends later. | numberor null |
running | boolean |
startedAtWhen the run under way started, or null. | date-timeor null |
positionSeconds into the show, while it runs. | numberor null |
scheduleWhen it starts by itself, as set in Administration → Sequences. Empty for a show started by hand. | ShowSchedule[] |
schedule[].daysThe days of the week it starts on. | monday | tuesday | wednesday | thursday | friday | saturday | sunday[] |
schedule[].time
| string |
schedule[].timeZoneThe park's time zone, by its IANA name: | string |
schedule[].fromThe first day it runs, or null. | stringor null |
schedule[].untilThe last day it runs, or null. The same as | stringor null |
schedule[].enabledFalse for a time switched off in the administration: kept, but it starts nothing. | boolean |
nextRunAtWhen 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.
curl -X POST https://connect.your-park.com/api/v1/shows/3/stop \ -H "Authorization: Bearer $UTP_API_KEY"
{
"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.
/api/v1/stats/ridesLaps per ride
The most ridden first. A ride since removed keeps the laps ridden on it.
Parameters
| Name | In | Type |
|---|---|---|
period
| query | day | week | month | all |
Answer · 200
A list of RideStats, each with:
| Field | Type |
|---|---|
rideId | integer |
nameThe ride's name, or | string |
laps | integer |
ridersDifferent 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.
curl https://connect.your-park.com/api/v1/stats/rides \ -H "Authorization: Bearer $UTP_API_KEY"
[
{
"rideId": 4,
"name": "Lost Bearings",
"laps": 318,
"riders": 187
},
{
"rideId": 7,
"name": "Wild Detour",
"laps": 141,
"riders": 102
}
]/api/v1/stats/top-ridersTop riders
The players with the most laps, on one ride or on the whole park — the list a leaderboard in the park shows.
Parameters
| Name | In | Type |
|---|---|---|
rideIdOne ride's riders. The whole park if left out. | query | integer |
period
| query | day | week | month | all |
limit | query | integer |
Answer · 200
A list of TopRider, each with:
| Field | Type |
|---|---|
rankFirst is 1. Players level on laps share a rank. | integer |
player | Player |
player.uuidThe player's Minecraft UUID. | string |
player.nameThe name they were last seen with. | string |
laps | 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.
curl https://connect.your-park.com/api/v1/stats/top-riders \ -H "Authorization: Bearer $UTP_API_KEY"
[
{
"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
}
]/api/v1/players/{player}A player's laps
Parameters
| Name | In | Type |
|---|---|---|
playerrequiredA UUID, or the name the player was last seen with. | path | string |
Answer · 200
| Field | Type |
|---|---|
uuid | string |
name | string |
lastSeenWhen the server last reported them online. | date-timeor null |
listeningTheir audio page is open right now. | boolean |
totalLaps | integer |
rides | object[] |
rides[].rideId | integer |
rides[].name | string |
rides[].laps | integer |
rides[].rankWhere 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.
curl https://connect.your-park.com/api/v1/players/Kaelith \ -H "Authorization: Bearer $UTP_API_KEY"
{
"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.
/api/v1/photosThe latest on-ride photos
Photos still within the retention set in the administration, newest first.
Parameters
| Name | In | Type |
|---|---|---|
rideIdOne ride's photos. | query | integer |
sinceOnly photos taken after this instant. Pass the last | query | date-time |
limit | query | integer |
Answer · 200
A list of Photo, each with:
| Field | Type |
|---|---|
id | string |
rideId | integer |
rideName | string |
takenAtAn instant, in ISO 8601, UTC. | date-time |
riders | Player[] |
riders[].uuidThe player's Minecraft UUID. | string |
riders[].nameThe name they were last seen with. | string |
pageUrlThe page the photo wall's QR code opens. Anyone with the link sees it, until the photo expires. | stringor null |
framesThe 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.
curl https://connect.your-park.com/api/v1/photos \ -H "Authorization: Bearer $UTP_API_KEY"
[
{
"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.
/api/v1/audio/playRead and write keyPlay 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
| Field | Type |
|---|---|
playerA player's name or UUID. | string |
urlAn MP3, OGG or M4A file anyone can download. | string |
volumeFrom 0 to 100. Left as it is if not given. | integer |
Answer · 200
| Field | Type |
|---|---|
player | Player |
player.uuidThe player's Minecraft UUID. | string |
player.nameThe name they were last seen with. | string |
listeningWhether 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.
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}'{
"player": {
"uuid": "3f8a2c1e-6b47-4d1a-9c2e-1a7b5e0d9f42",
"name": "Kaelith"
},
"listening": true
}/api/v1/audio/stopRead and write keyStop the audio of a player
What /utp audio stop does.
Body
| Field | Type |
|---|---|
playerA player's name or UUID. | string |
Answer · 200
| Field | Type |
|---|---|
player | Player |
player.uuidThe player's Minecraft UUID. | string |
player.nameThe name they were last seen with. | string |
listeningWhether 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.
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"}'{
"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
| Name | In | Type |
|---|---|---|
webhook-idrequiredThe message's identifier, the same on every retry: use it to ignore a message you already have. | header | string |
webhook-timestamprequiredWhen this attempt was sent, in Unix seconds. Refuse a message more than five minutes old. | header | integer |
webhook-signaturerequired
| header | string |
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.completedA 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
| Field | Type |
|---|---|
rideId | integer |
rideName | string |
ridersEverybody aboard for the lap. | Player[] |
riders[].uuidThe player's Minecraft UUID. | string |
riders[].nameThe name they were last seen with. | string |
{
"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.takenAn 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
| Field | Type |
|---|---|
id | string |
rideId | integer |
rideName | string |
takenAtAn instant, in ISO 8601, UTC. | date-time |
riders | Player[] |
riders[].uuidThe player's Minecraft UUID. | string |
riders[].nameThe name they were last seen with. | string |
pageUrlThe page the photo wall's QR code opens. Anyone with the link sees it, until the photo expires. | stringor null |
framesThe pictures, one per group of rows, as images anyone with the link can load. | string[] |
{
"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.stoppedA 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
| Field | Type |
|---|---|
rideId | integer |
rideName | string |
byThe operator who pressed it, or null when the ride stopped itself. | Playeror null |
by.uuidThe player's Minecraft UUID. | string |
by.nameThe name they were last seen with. | string |
{
"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.resumedA stopped ride runs again
The emergency stop was released.
data
| Field | Type |
|---|---|
rideId | integer |
rideName | string |
byThe operator who released it, or null. | Playeror null |
by.uuidThe player's Minecraft UUID. | string |
by.nameThe name they were last seen with. | string |
{
"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.changedA 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
| Field | Type |
|---|---|
rideId | integer |
rideName | string |
availabilityWhether the ride takes guests, as its owner set it. | open | closed | maintenance | broken |
previousHow it stood before. | open | closed | maintenance | broken |
byThe player who changed it, in game or in the administration; null from the server's console or through this API. | Playeror null |
by.uuidThe player's Minecraft UUID. | string |
by.nameThe name they were last seen with. | string |
{
"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.upcomingA 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
| Field | Type |
|---|---|
showId | integer |
showName | string |
showLocationWhere guests find it, or null. | stringor null |
startsAtWhen it starts, on its schedule. | date-time |
{
"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.startedA 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
| Field | Type |
|---|---|
showId | integer |
showName | string |
showLocationWhere guests find it, or null. | stringor null |
trigger
| schedule | admin | api |
scheduledForThe time it was scheduled for, when | date-timeor null |
{
"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.skippedA 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
| Field | Type |
|---|---|
showId | integer |
showName | string |
showLocationWhere guests find it, or null. | stringor null |
scheduledForThe time it was scheduled for. | date-time |
reason
| nobody_online | already_running | server_offline | not_ready | no_answer |
{
"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.onlineThe Minecraft server is connected
Sent when the server comes back after server.offline, and when the instance itself has restarted.
{
"id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
"type": "server.online",
"timestamp": "2026-10-11T20:11:49.000Z",
"data": {}
}server.offlineThe Minecraft server is gone
Sent when the server stops, or when nothing has been heard from it for twenty seconds.
data
| Field | Type |
|---|---|
reason
| stopping | lost |
{
"id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
"type": "server.offline",
"timestamp": "2026-10-11T20:11:49.000Z",
"data": {
"reason": "stopping"
}
}webhook.testA test
Sent by the Send a test button in the administration, to this webhook alone.
data
| Field | Type |
|---|---|
message | string |
{
"id": "msg_01K7AXM5Q3Z8C1V2B3N4M5P6Q7",
"type": "webhook.test",
"timestamp": "2026-10-11T20:11:49.000Z",
"data": {
"message": "This is a test from your park's administration."
}
}