Skip to main content
Park Queue TimesPQT

Public API · v1

Build with live
theme park data.

Live queue times, opening hours and crowd forecasts for 100+ parks, behind one set of JSON endpoints. Free to use - the only cost is an attribution link back to us.

100+ parks4149+ ridesHistory back to Jan 2024OpenAPI 3.1

Introduction

One read-only JSON API over everything this site runs on: which parks exist, what every ride's queue is right now, when each park is open, how busy we expect each day to be, and what those queues were in the past.

Base URL
https://api.parkqueuetimes.com
Protocol
HTTPS only. Every endpoint is a GET and returns JSON.
Authentication
An x-api-key header on every request. Keys are free.
Versioning
The surface is /v1. Additive changes ship in place; a breaking one would ship as /v2.
Machine-readable
OpenAPI 3.1 at /v1/openapi.json - no key needed, so you can point a code generator at it right now.
Coverage
100+ parks and 4149+ rides across Europe, North America and Asia. Browse the list.

Quickstart

Three steps, about a minute, no card.

  1. Create a free account

    No card, no trial - register with an email address, or sign in with Google or Apple.

  2. Create an API key

    Head to API keys in your dashboard and create one. You can view and copy it from there any time.

  3. Call the API

    Send the key in the x-api-key header and you are live:

bash
curl -H "x-api-key: pqt_live_..." \
  https://api.parkqueuetimes.com/v1/parks

That is the whole integration. Every response carries a ready-made credit string in meta.attribution - show it near the data and you have met the one condition of the free tier. Next: how keys travel, or jump straight to the endpoint reference.

Authentication

One key per account, sent on every request.

Create your key at Developer in your dashboard with any account. You can view and copy it from the dashboard whenever you want, and revoke and recreate it as often as you need - there is no cooldown and nothing resets, because your rate limits, your plan and your usage history belong to the account rather than to the key.

Send it in the x-api-key header. The ?api_key= query parameter also works, which is handy for a quick test in a browser, but prefer the header in anything real: query strings end up in server logs, proxy logs and browser history.

bash
curl -H "x-api-key: pqt_live_..." \
  https://api.parkqueuetimes.com/v1/parks/3/live

A key identifies your account, not one of your users. Keep it on a server you control. If one does leak, revoke it and make another - it takes a second and costs you no history.

One favour, not a rule: send a User-Agent that names your project, like MyParkApp/1.0 (https://myapp.com). Because you should be calling us from your own server, that header is the only thing that tells us your requests apart from everyone else's default node or python-requests. It means we can warn you before something changes, and see that you are attributing us properly. Adding the URL where your key runs in the dashboard does the same job.

Response format

Every endpoint answers in the same envelope, so one parser and one error handler cover the whole API.

The shape of every successful response
json
{
  "success": true,
  "data": { … },
  "meta": {
    "version": "1.0",
    "generated": "2026-06-11T14:32:08.000Z",
    "attribution": "Powered by ParkQueueTimes.com - https://parkqueuetimes.com",
    "requestId": "req_8f2c1ad4"
  }
}

data is the only part that changes shape between endpoints. meta always carries these four:

version
The contract version this response was built against, currently 1.0.
generated
When we produced the response, as an ISO 8601 timestamp in UTC.
attribution
The exact credit string to display. See Attribution.
requestId
Correlates this response with our logs. Quote it if you report a problem.

Some endpoints add one more - total on the park list, count and nextCursor on history, and access on history coverage. Additions are not breaking changes: new fields can appear inside data or meta at any time, so parse defensively and ignore what you do not recognise. Anything that would remove or repurpose a field would ship as /v2 instead.

Rate limits

Lenient by the minute, firm by the day - and every keyed response tells you exactly where you stand.

Your allowance depends on your plan - 60 requests a minute and 5,000 a day on the free tier, more on Developer and Archive. Limits are counted per account, not per key.

The per-minute limit is enforced leniently: a brief burst above your allowance will not cut you off, and only sustained traffic well above it earns a 429 Too Many Requests. Please do not design around that headroom - it is a courtesy rather than part of your allowance, and the published number is the one we enforce against.

The daily limit is firm. Once your account has used its allowance for the day, further requests return 429 with the code DAILY_LIMIT_EXCEEDED until it resets. Every 429 carries a Retry-After header in seconds - for a daily limit that is the time left in the day, so you can sleep on it rather than poll.

HeaderMeaning
X-RateLimit-LimitYour per-minute allowance on the current plan.
X-RateLimit-RemainingRequests left in the current minute.
X-RateLimit-ResetUnix timestamp, in seconds, when the current minute window ends.
X-RateLimit-Daily-LimitYour allowance for the day, counted across every key on the account.
X-RateLimit-Daily-RemainingRequests left today.
X-RateLimit-Daily-ResetUnix timestamp, in seconds, when the daily allowance resets.
Retry-AfterSent only with a 429. How many seconds to wait before trying again.

Keyed responses are never cached (Cache-Control: no-store), so a live wait time is always the freshest reading we hold rather than something an edge served you.

Errors

Errors use one envelope too, with a machine-readable code and the request id to quote if you need to ask us about it.

403 Forbidden
json
{
  "success": false,
  "error": "A free key can read history from 2026-06-09 onwards.",
  "code": "HISTORY_TIER_REQUIRED",
  "requestId": "req_8f2c1ad4"
}

The error string is written for a human and its wording may change. Branch on code:

StatusCodeWhat it means, and what to do
400BAD_REQUESTA malformed parameter - a bad date, an unknown granularity, or a history range wider than one request allows. The error names which one.
401UNAUTHORIZEDMissing, invalid or revoked key. Check the x-api-key header; keys are free if you need one.
403HISTORY_TIER_REQUIREDYour plan cannot read that far back. The error names the earliest date you can fetch - see Plans.
404PARK_NOT_FOUNDUnknown park id or slug. List valid ids with /v1/parks.
429RATE_LIMITEDSustained traffic well above your per-minute limit. Back off for the number of seconds in Retry-After.
429DAILY_LIMIT_EXCEEDEDYour account has used its allowance for the day. Retry-After gives the seconds until it resets; a larger allowance comes with Developer or Archive.
503UNAVAILABLEPark data is still loading or briefly unavailable. Retry after a short delay.

A 5xx is ours, not yours - retry with a short backoff, and send us the requestId if it keeps happening.

Pagination

Only history pages. Every other endpoint returns its whole result in one response.

One history request covers at most 31 days hourly, 366 days daily or 1,830 days monthly — so a monthly series reaches back to January 2024 in a single call — and returns one page of rows. When meta.nextCursor comes back non-null there is more: repeat the identical request with &cursor= set to that value. When it is null, the window has been fully delivered.

Draining a window
javascript
let cursor = null
const rows = []

do {
  const url = new URL('https://api.parkqueuetimes.com/v1/parks/3/history')
  url.searchParams.set('from', '2026-06-01')
  url.searchParams.set('to', '2026-07-01')
  if (cursor) url.searchParams.set('cursor', cursor)

  const res = await fetch(url, { headers: { 'x-api-key': process.env.PQT_KEY } })
  const body = await res.json()
  if (!body.success) throw new Error(body.error)

  rows.push(...body.data.rows)
  cursor = body.meta.nextCursor
} while (cursor)

Cursors are opaque and short-lived - do not store one or try to build one yourself. To resume a backfill days later, remember the last date you wrote and start a fresh window from there.

Attribution

The one condition attached to a free key.

Free use requires displaying "Powered by ParkQueueTimes.com" linking to https://parkqueuetimes.com somewhere prominent in your app or service.

To make that easy, every successful response includes the exact string to show, in meta.attribution. Display it near the data and you are done. Apps and sites using our data without attribution may have their keys revoked. Paid plans drop the requirement.

Plans

Live data is free for everyone. Paid plans buy the full crowd forecast, history - how far back into the per-ride archive your key can read - higher limits and commercial use without attribution. All three license the data for your own product and your own analysis; publishing it or supplying it onward is Enterprise.

Free

For trying the API and small projects.

£0GBP / monthFree forever, no card needed

Create your free key

  • Live wait times for every park
  • Opening hours and the next 14 days of the crowd forecast
  • Last 48 hours of ride history
  • 60 requests a minute, 5,000 a day
  • Commercial use with an attribution link

Developer

For apps and sites built on the data.

£15GBP / month£190 billed yearly

No commitment, cancel any time

  • Everything in Free and:
  • The full crowd forecast, as far ahead as we predict
  • Rolling 90 days of ride history
  • 300 requests a minute, 50,000 a day
  • Commercial use, no attribution needed
  • Every key on your account upgraded at once

Checkout is tied to the account you are signed in with, and your existing key is upgraded automatically within a minute of paying - no new key, no code change. What you may and may not do with the data is set out in the API data licence - in short, build what you like on it, publish what you conclude from it, but do not re-supply the data itself. Anything beyond that is Enterprise.

Compare every detail
FeatureFreeDeveloperArchiveEnterprise
HistoryLast 48 hoursRolling 90 daysEverything, from Jan 2024Everything, from Jan 2024
Crowd forecastNext 14 daysFull horizonFull horizonFull horizon
GrainHourly, daily or monthlyHourly, daily or monthlyHourly, daily or monthlyAny, including bespoke cuts
Requests per minute60300600Agreed
Requests per day5,00050,000200,000Agreed
Commercial useWith attributionIncludedIncludedIncluded
LicenceYour own product and analysisYour own product and analysisYour own product and analysisPublish and supply onward
Attribution linkRequiredOptionalOptionalOptional
BillingFree foreverMonthly or yearlyYearlyPer project

On Free, Developer and Archive, forecasts and recommendations derived from the data inside your own product are fine, and so is publishing what you conclude from it; re-supplying the data itself - as a feed, a dataset, or a deliverable someone else pays you for - is Enterprise.

API reference

Seven endpoints, all GET, all returning the same envelope.

Base URL
https://api.parkqueuetimes.com
{id}
The numeric park id from /v1/parks. A park slug is accepted as a convenience alias, but ids are stable and slugs are not - use the id in anything you store.
Samples
Every request below reads your key from a PQT_KEY environment variable, so you can export yours once and paste any of them straight into a terminal.

GET/v1/parks

Every park we track, with its location, timezone and links to its own sub-resources. Start here: the ids in this response address every other endpoint.

meta.total carries the number of parks returned.

Returns

idinteger
The canonical park id. Stable - store this one.
namestring
Display name.
slugstring
URL-safe alias. Accepted in place of an id, but it can change - never store it as a key.
countrystring
ISO country code.
latitude, longitudenumber
Park centre, in decimal degrees.
timezonestring
IANA zone. Opening hours and history buckets are in this zone.
linksobject
Absolute URLs for this park’s self, live, schedule and calendar resources.
bash
curl -H "x-api-key: $PQT_KEY" \
  "https://api.parkqueuetimes.com/v1/parks"

GET/v1/parks/{id}

One park, plus whether it is open right now and today’s hours.

Every field from the park list is present here too.

Path parameters

idinteger | stringrequired
Park id, or slug.

Returns

statusstring
OPEN or CLOSED, for the park as a whole.
hoursobject | null
Today’s opening and closing times as ISO 8601 with the park’s own UTC offset. null on a day the park does not open, or when we hold no hours for it.
bash
curl -H "x-api-key: $PQT_KEY" \
  "https://api.parkqueuetimes.com/v1/parks/3"

GET/v1/parks/{id}/live

Live standby wait times for every ride in the park, as of the park’s most recent publication to its own feed.

There is no fixed refresh cycle: readings change when a park publishes a change. Polling faster than about once a minute per park will not get you fresher data.

Path parameters

idinteger | stringrequired
Park id, or slug.

Returns

statusstring
One of OPERATING, DOWN, CLOSED, REFURBISHMENT.
waitMinutesinteger | null
Standby queue in minutes. null means the ride is publishing no number - which is not the same as a zero-minute walk-on. Never render a null as 0.
lastUpdatedstring | null
When the park last changed this ride’s reading, ISO 8601 UTC.
bash
curl -H "x-api-key: $PQT_KEY" \
  "https://api.parkqueuetimes.com/v1/parks/3/live"

GET/v1/parks/{id}/schedule

The park’s operating calendar - one entry per scheduled day, in the park’s own timezone.

Path parameters

idinteger | stringrequired
Park id, or slug.

Query parameters

datestring
Month to return, as YYYY-MM. Past and future months both work. Defaults to the current month; a malformed value is a 400 rather than a silent fallback.

Returns

datestring
YYYY-MM-DD, park-local.
openingTime, closingTimestring
ISO 8601 with the park’s UTC offset. A closing time can fall after midnight.
typestring
Usually OPERATING. Special windows, such as ticketed evening events, carry their own type.
bash
curl -H "x-api-key: $PQT_KEY" \
  "https://api.parkqueuetimes.com/v1/parks/3/schedule?date=2026-07"

GET/v1/parks/{id}/calendarFree key: next 14 days

Predicted crowd levels per day, from our Almanac forecasting model.

For a date that has already happened the figure is measured rather than predicted, so the same endpoint serves both.

Crowd levels are comparable within one park across dates, not between parks - 70 at a quiet park is not the same queue as 70 at a busy one.

A free key reads the past and the next 14 days; dates beyond that are left out of days, and meta.access.until says where your horizon ends. A Developer or Archive key reads the whole horizon.

Path parameters

idinteger | stringrequired
Park id, or slug.

Query parameters

datestring
Month to return, as YYYY-MM. Past and future months both work. Defaults to the current month; a malformed value is a 400 rather than a silent fallback.

Returns

crowdPercentinteger | null
0-100, higher is busier, scored against this park’s own busiest day rather than against other parks. null where we have no forecast for that date.
bash
curl -H "x-api-key: $PQT_KEY" \
  "https://api.parkqueuetimes.com/v1/parks/3/calendar?date=2026-07"

GET/v1/parks/{id}/history/coverageFree on every key

What history we hold for a park, ride by ride - and, in meta.access, how far back your own key may actually read it.

This endpoint is deliberately free on every tier: diff it against your own store to see exactly which windows you are missing before deciding whether a paid plan is worth it.

Path parameters

idinteger | stringrequired
Park id, or slug.

Returns

firstDate, lastDatestring
The park-local window we hold anything at all for.
rides[].days, rides[].hoursinteger
How many distinct days and hours we hold for that ride - so you can tell a fully covered ride from a patchy one before you pay for either.
meta.access.fromstring
The earliest date your key may fetch. On a free key this is two days ago and meta.access.upgrade is also present.
bash
curl -H "x-api-key: $PQT_KEY" \
  "https://api.parkqueuetimes.com/v1/parks/3/history/coverage"

GET/v1/parks/{id}/historyFree key: last 48h

Historical wait times, bucketed in the park’s own calendar time — by hour, day or month, per ride or for the park as a whole.

Read the counts, not just avgWait. A genuine walk-on is avgWait: 0 with waitReadings above zero; a closed ride is openReadings: 0; and a missing row means we collected nothing at all, which is not a zero wait. Most datasets collapse those three together, and that quietly biases any model fitted on them.

Requesting a from older than your plan allows is a 403 that names the earliest date you can fetch - it never silently truncates the window.

A park-level avgWait is weighted by observation, not by ride: every reading counts once, so a ride that runs all day carries more of the average than one that opens for an hour. That is the same figure however you bucket it, because we aggregate from a stored sum rather than averaging averages. /v1/parks/3/history?from=2024-01-01&to=2026-10-01&granularity=month&groupBy=park returns one row per month for the whole archive.

Path parameters

idinteger | stringrequired
Park id, or slug.

Query parameters

fromstringrequired
First day to include, YYYY-MM-DD, park-local.
tostringrequired
End of the window, YYYY-MM-DD, exclusive.
granularitystring
hour (default), day or month. One request covers up to 31 days hourly, 366 daily, or 1,830 monthly.
groupBystring
ride (default) for one series per ride, or park to collapse every ride into a single series for the park. granularity=month&groupBy=park is one average wait per park per month — the shape most research and benchmarking work wants.
rideinteger
Restrict to a single ride id.
cursorstring
The meta.nextCursor from the previous page. See Pagination.
limitinteger
Maximum rows in this page. Lower it if you are memory-bound; you cannot raise it past our own cap.

Returns

date, monthstring
The bucket, park-local. date (YYYY-MM-DD) at hourly and daily grain; month (YYYY-MM) at monthly.
hourinteger
Hour of the day, 0-23, park-local. Present only when granularity=hour; a daily or monthly row carries openHours instead, and a monthly row also carries openDays.
ridesinteger
Only when groupBy=park, in place of rideId and rideName: how many rides actually posted a wait in the bucket. Watch it — a month where it drops is a month our coverage thinned, not a quieter month.
readingsinteger
Samples we collected in the bucket, whatever the ride was doing.
openReadingsinteger
Of those, how many had the ride open. 0 means it was closed for the whole bucket.
waitReadingsinteger
Of those, how many carried an actual number. Open with waitReadings: 0 means the ride published no wait.
avgWait, peakWaitnumber | null
Mean and maximum standby wait across waitReadings. null when there were none.
bash
curl -H "x-api-key: $PQT_KEY" \
  "https://api.parkqueuetimes.com/v1/parks/3/history?from=2026-06-11&to=2026-06-12"

Frequently asked questions

Is the API free?

Yes. Free keys include 60 requests per minute and 5,000 per day - plenty for most apps, bots and dashboards. The only requirement is the attribution link.

How fresh is the data?

Live wait times update as soon as each park publishes a change to its official feed - there is no fixed refresh cycle. Keyed /v1 responses are never cached, so you always see the latest reading we hold.

Can I use it commercially?

Yes - commercial use is welcome on the free tier, as long as the "Powered by ParkQueueTimes.com" attribution is displayed prominently in your app or service. Paid plans drop the requirement.

Is historical data available?

Yes - wait history back to January 2024, hourly, daily or monthly, per ride or for the park as a whole, in each park's own calendar time. Coverage is free on every key and shows exactly what we hold; the rows themselves are a paid plan, with the last 48 hours open on a free key so you can build against real data first.

Can I get one average wait per park per month?

In one request: granularity=month&groupBy=park on history. That is the shape most benchmarking, research and trend work needs, and it saves re-deriving it from ride-level rows - which is easy to get subtly wrong, because averaging per-ride averages is not the same number as weighting by observation. Coverage depth varies by park, so check coverage first: around 29 parks reach back into 2024 and the rest start later.

What do the paid plans cost?

Two, plus a licence for anything published. Developer is £19 a month (£190 a year): the full crowd forecast horizon, a rolling 90 days of ride history, 300 requests a minute and commercial use without attribution - the plan for an app or a site built on the data. Archive is £490 a year for the full series back to January 2024 at 600 a minute - for fitting a crowd model on the whole thing. Derived forecasts are fine on both; redistributing the data as a competing feed is not. Research, consultancy and published work are an Enterprise licence, from £1,500 per project. Live wait times, opening hours and the next 14 days of the forecast stay free.

What does a zero wait mean in the history?

Exactly what it says - but read the counts, not just avgWait. openReadings: 0 means the ride was closed. openReadings above zero with waitReadings: 0 means it was open but publishing no number. avgWait: 0 with waitReadings above zero is a genuine walk-on. A missing row means we collected no reading at all - not a zero wait.

Can I generate a client from the spec?

Yes. The full OpenAPI 3.1 description is at /v1/openapi.json and needs no key, so you can point openapi-generator, Swagger UI or your editor at it directly.

How many parks are covered?

Around 100 parks and 4149+ rides across the UK, Europe, North America and Asia, with more added regularly. Browse the full list.

Where do I report issues or request features?

Email [email protected] - we read everything, and developer feedback shapes what we build next.

Ready to build?

Your first key takes less than a minute. Questions, bug reports and feature requests all go to [email protected] - we read everything, and developer feedback shapes what we build next.