Live updates
Instead of polling /waits, open one connection: Parky sends the whole board once, then only what changes. The server checks every 5 seconds and pushes a ride only when Disney or Universal changes it; posted waits usually change every few minutes, so quiet minutes are normal.
WebSocket
Connect to wss://api.parkypass.com/v1/stream, wait for ready, then subscribe to up to 10 parks, or "*" for all of them. Send a key as ?key= or an Authorization header on the upgrade. This page keeps Magic Kingdom live:
<ul id="parky-waits"></ul><!-- Required: keep the "Live data from parkypass.com" link visible on every page that shows this data. --><a id="parky-credit" href="https://parkypass.com/">Live data from parkypass.com</a><script> // Add ?key=pk_live_... to the URL for higher limits (optional). const rides = new Map(); const list = document.getElementById("parky-waits"); const credit = document.getElementById("parky-credit"); const socket = new WebSocket("wss://api.parkypass.com/v1/stream"); function render(message) { // Required: keep the "Live data from parkypass.com" link visible on every page that shows this data. credit.href = message.attribution.url; credit.textContent = message.attribution.text; if (message.type === "snapshot") rides.clear(); for (const ride of message.rides) rides.set(ride.ride_id, ride); list.replaceChildren(...Array.from(rides.values(), (ride) => { const item = document.createElement("li"); item.textContent = ride.name + ": " + (ride.wait_minutes !== null ? ride.wait_minutes + " min" : ride.status); return item; })); } socket.onmessage = (event) => { const message = JSON.parse(event.data); // Subscribe only once the server says it is ready. if (message.type === "ready") socket.send(JSON.stringify({ type: "subscribe", parks: ["magic-kingdom"] })); if (message.type === "snapshot" || message.type === "changes") render(message); };</script>Key your rides by ride_id. A snapshot replaces everything for its park; each changes message carries only the rides that changed, each in full, so you can replace them as they come.
Messages you send
- subscribe
- Up to 10 parks, written any way {park} accepts, or "*" for every park.
{"type":"subscribe","parks":["magic-kingdom","epcot"]} - unsubscribe
- Stop following some parks.
{"type":"unsubscribe","parks":["epcot"]} - ping
- Optional; the server answers with pong.
{"type":"ping"}
Messages Parky sends
Every message has type, attribution and generated_at.
- ready
- WebSocket only, on connect: authorized, send subscribe next.
{"type":"ready","max_parks":10,"idle_timeout_seconds":120,…} - subscribed
- Your current parks, after each subscribe; unsubscribed after each unsubscribe.
{"type":"subscribed","parks":["magic-kingdom","epcot"],…} - snapshot
- Once per park on subscribe: the whole board, in the same shape as /waits, plus park and park_key.
{"type":"snapshot","park":"magic-kingdom","park_key":"magic-kingdom","data_status":"live","park_open":true,"rides":[…],"attribution":{…},…} - changes
- Only the entries whose status, wait, Single Rider or Lightning Lane changed, each in full. The server checks every 5 seconds and pushes a ride only when Disney or Universal changes it; posted waits usually change every few minutes, so quiet minutes are normal.
{"type":"changes","park":"magic-kingdom","park_key":"magic-kingdom","data_status":"live","rides":[…],"attribution":{…},…} - park
- When a park opens or closes.
{"type":"park","park":"magic-kingdom","park_key":"magic-kingdom","park_open":false,"data_status":"live",…} - ping
- Every 25 seconds. Server-Sent Events send a ": ping" comment line instead.
{"type":"ping",…} - error
- A rejected subscription or connection, with the same codes as REST errors. The socket stays open unless a close follows.
{"type":"error","error":{"code":"not_found","message":"…","docs_url":"…"},…}
Close codes
- 1000
- idle: no subscriptions for 2 minutes.
- 1001
- The server is restarting. Reconnect.
- 1008
- invalid_key or rights_unavailable. Don't retry unchanged.
- 1011
- Internal error. Reconnect with backoff.
- 1013
- rate_limited, too_many_streams or upstream_unavailable. Retry later.
A socket with no subscriptions closes after 2 minutes, and a client that falls about 1 MB behind is dropped. Reconnect with a short backoff and subscribe again.
Server-Sent Events
For one park, /v1/parks/{park}/waits/stream sends the same messages from subscribed on, as unnamed data: lines, so onmessage receives them all. It adds retry: 5000 and a : ping comment every 25 seconds, and there is no ready.
<ul id="parky-waits"></ul><!-- Required: keep the "Live data from parkypass.com" link visible on every page that shows this data. --><a id="parky-credit" href="https://parkypass.com/">Live data from parkypass.com</a><script> // Add ?key=pk_live_... to the URL for higher limits (optional). const rides = new Map(); const list = document.getElementById("parky-waits"); const credit = document.getElementById("parky-credit"); const events = new EventSource("https://api.parkypass.com/v1/parks/magic-kingdom/waits/stream"); function render(message) { // Required: keep the "Live data from parkypass.com" link visible on every page that shows this data. credit.href = message.attribution.url; credit.textContent = message.attribution.text; if (message.type === "snapshot") rides.clear(); for (const ride of message.rides) rides.set(ride.ride_id, ride); list.replaceChildren(...Array.from(rides.values(), (ride) => { const item = document.createElement("li"); item.textContent = ride.name + ": " + (ride.wait_minutes !== null ? ride.wait_minutes + " min" : ride.status); return item; })); } // Server-Sent Events subscribe to the park in the URL; there is no ready message. events.onmessage = (event) => { const message = JSON.parse(event.data); if (message.type === "snapshot" || message.type === "changes") render(message); };</script>Limits
Streams open at once: 2 per IP without a key, 10 per free key, 100 per partner key. Each connection counts once against the per-minute request limit. Over the limit, the connection is refused with too_many_streams.