{
  "openapi": "3.1.0",
  "info": {
    "title": "Parkstack API",
    "version": "1",
    "description": "Read a park's published details and sell its tickets from the park's own website. Every request needs a site key from the park's owner (Park settings → Your own website). Within v1, fields are only ever added: none is renamed, removed or changed in meaning."
  },
  "servers": [
    {
      "url": "https://api.parkstack.net/site/v1"
    }
  ],
  "security": [
    {
      "siteKey": []
    }
  ],
  "paths": {
    "/park": {
      "get": {
        "operationId": "park",
        "summary": "The park",
        "description": "Name, contact details, address, logo, colours, fonts, the cheapest ticket and who guests buy from. Start here: most pages need some of it.\n\nShow `seller` beside anything that sells tickets, and link the Ticket terms (`/pages/ticket-terms`).",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "The park's name as guests know it."
                    },
                    "tagline": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "A short line under the name."
                    },
                    "timezone": {
                      "type": "string",
                      "description": "IANA time zone; every date and time in the API is on this clock."
                    },
                    "currency": {
                      "type": "string",
                      "description": "ISO 4217 code, \"GBP\". Every amount is in this currency's smallest unit (pence)."
                    },
                    "contact": {
                      "type": "object",
                      "properties": {
                        "email": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "phone": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "email",
                        "phone"
                      ],
                      "additionalProperties": false,
                      "description": "For guests to get in touch."
                    },
                    "address": {
                      "type": "object",
                      "properties": {
                        "lines": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The postal address; may hold line breaks."
                        },
                        "postcode": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "parking": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The park's note on parking."
                        },
                        "mapUrl": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "A Google Maps search for the address."
                        }
                      },
                      "required": [
                        "lines",
                        "postcode",
                        "parking",
                        "mapUrl"
                      ],
                      "additionalProperties": false
                    },
                    "social": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "label": {
                            "type": "string",
                            "description": "\"Facebook\", \"Instagram\", \"TikTok\", \"X\" or the park's own words."
                          },
                          "url": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "label",
                          "url"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Social media and other links, in the park's order."
                    },
                    "logo": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "url": {
                          "type": "string",
                          "description": "The full-size file."
                        },
                        "width": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                        },
                        "height": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known."
                        },
                        "alt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Null means decorative: give it alt=\"\"."
                        },
                        "srcset": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "For an <img srcset>, when the file can be served at smaller sizes."
                        }
                      },
                      "required": [
                        "url",
                        "width",
                        "height",
                        "alt",
                        "srcset"
                      ],
                      "additionalProperties": false
                    },
                    "logoForDarkBackgrounds": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "url": {
                          "type": "string",
                          "description": "The full-size file."
                        },
                        "width": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                        },
                        "height": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known."
                        },
                        "alt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Null means decorative: give it alt=\"\"."
                        },
                        "srcset": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "For an <img srcset>, when the file can be served at smaller sizes."
                        }
                      },
                      "required": [
                        "url",
                        "width",
                        "height",
                        "alt",
                        "srcset"
                      ],
                      "additionalProperties": false,
                      "description": "A white or light logo for dark backgrounds, when the park has one."
                    },
                    "colours": {
                      "type": "object",
                      "properties": {
                        "main": {
                          "type": "string",
                          "description": "A hex colour, \"#0f5c4c\"."
                        },
                        "accent": {
                          "type": "string",
                          "description": "A hex colour."
                        }
                      },
                      "required": [
                        "main",
                        "accent"
                      ],
                      "additionalProperties": false,
                      "description": "Hints for a matching design; the site is free to ignore them."
                    },
                    "fonts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "role": {
                            "type": "string",
                            "enum": [
                              "Headings",
                              "Text",
                              "Headings and text"
                            ]
                          },
                          "name": {
                            "type": "string",
                            "description": "A Google Fonts family name."
                          },
                          "url": {
                            "type": "string",
                            "description": "Its Google Fonts stylesheet."
                          }
                        },
                        "required": [
                          "role",
                          "name",
                          "url"
                        ],
                        "additionalProperties": false
                      },
                      "description": "The fonts the park chose; the site is free to use others."
                    },
                    "tickets": {
                      "type": "object",
                      "properties": {
                        "onSaleOnline": {
                          "type": "boolean",
                          "description": "Whether guests can buy tickets online now. Hide ticket links while false."
                        },
                        "fromPence": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "The cheapest ticket on sale, in pence: show it as the total price (\"From £12\")."
                        },
                        "fromIncludesVat": {
                          "type": [
                            "boolean",
                            "null"
                          ],
                          "description": "Whether the \"from\" price includes VAT (\"From £12 inc. VAT\"); null when there is no \"from\" price."
                        },
                        "shopUrl": {
                          "type": "string",
                          "description": "Parkstack's ticket shop, until the site sells tickets itself."
                        }
                      },
                      "required": [
                        "onSaleOnline",
                        "fromPence",
                        "fromIncludesVat",
                        "shopUrl"
                      ],
                      "additionalProperties": false
                    },
                    "seller": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "address": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "email": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "phone": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "vatNumber": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "companyDetails": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "For a limited company: name, where registered, number and registered office, in one sentence."
                        }
                      },
                      "required": [
                        "name",
                        "address",
                        "email",
                        "phone",
                        "vatNumber",
                        "companyDetails"
                      ],
                      "additionalProperties": false,
                      "description": "Who guests buy tickets from; show it with the ticket terms. Null fields are not set."
                    },
                    "parkstackUrl": {
                      "type": "string",
                      "description": "Parkstack's own address for the park's guest pages (ticket confirmations and legal pages live there)."
                    },
                    "demo": {
                      "type": "boolean",
                      "description": "A demonstration park: checkout takes no payment and its tickets are not valid."
                    }
                  },
                  "required": [
                    "name",
                    "tagline",
                    "timezone",
                    "currency",
                    "contact",
                    "address",
                    "social",
                    "logo",
                    "logoForDarkBackgrounds",
                    "colours",
                    "fonts",
                    "tickets",
                    "seller",
                    "parkstackUrl",
                    "demo"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "name": "Riverside Adventure Park",
                  "tagline": "Thrills on the river. Fun for every generation.",
                  "timezone": "Europe/London",
                  "currency": "GBP",
                  "contact": {
                    "email": null,
                    "phone": null
                  },
                  "address": {
                    "lines": null,
                    "postcode": null,
                    "parking": null,
                    "mapUrl": null
                  },
                  "social": [],
                  "logo": {
                    "url": "https://media.parkstack.net/riverside/1790686420307-78slhb.webp",
                    "width": 640,
                    "height": 640,
                    "alt": null,
                    "srcset": "https://media.parkstack.net/riverside/1790686420307-78slhb.webp?w=96 96w, https://media.parkstack.net/riverside/1790686420307-78slhb.webp?w=192 192w, https://media.parkstack.net/riverside/1790686420307-78slhb.webp?w=384 384w, https://med…"
                  },
                  "logoForDarkBackgrounds": null,
                  "colours": {
                    "main": "#0F5C4C",
                    "accent": "#E3B23C"
                  },
                  "fonts": [
                    {
                      "role": "Headings",
                      "name": "Fraunces",
                      "url": "https://fonts.google.com/specimen/Fraunces"
                    },
                    {
                      "role": "Text",
                      "name": "Source Sans 3",
                      "url": "https://fonts.google.com/specimen/Source+Sans+3"
                    }
                  ],
                  "tickets": {
                    "onSaleOnline": true,
                    "fromPence": 2900,
                    "fromIncludesVat": false,
                    "shopUrl": "https://riverside.parkstack.net/tickets"
                  },
                  "seller": {
                    "name": null,
                    "address": null,
                    "email": null,
                    "phone": null,
                    "vatNumber": null,
                    "companyDetails": null
                  },
                  "parkstackUrl": "https://riverside.parkstack.net",
                  "demo": true
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hours": {
      "get": {
        "operationId": "hours",
        "summary": "Opening hours",
        "description": "Opening times day by day, with special dates and the names of events on each day. Today when no dates are given; up to 62 days a request.\n\nA day with status `not_set` has no hours from the park: say nothing about it rather than \"closed\".",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "First day, YYYY-MM-DD. Today when left out.",
            "schema": {
              "type": "string"
            },
            "example": "2026-11-01"
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Last day, YYYY-MM-DD. The same as from when left out.",
            "schema": {
              "type": "string"
            },
            "example": "2026-11-30"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "days": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "date": {
                            "type": "string",
                            "description": "YYYY-MM-DD on the park's clock."
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "open",
                              "closed",
                              "not_set"
                            ],
                            "description": "\"not_set\" means the park has not given hours for that day: say nothing rather than \"closed\"."
                          },
                          "opens": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "HH:mm, or null."
                          },
                          "closes": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "lastEntry": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "label": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The day's label and the events on it (\"Halloween Trail\")."
                          }
                        },
                        "required": [
                          "date",
                          "status",
                          "opens",
                          "closes",
                          "lastEntry",
                          "label"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "days"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "days": [
                    {
                      "date": "2026-10-29",
                      "status": "open",
                      "opens": "10:00",
                      "closes": "21:00",
                      "lastEntry": "19:30",
                      "label": "Halloween late opening · Halloween Trail"
                    },
                    {
                      "date": "2026-10-30",
                      "status": "open",
                      "opens": "10:00",
                      "closes": "21:00",
                      "lastEntry": "19:30",
                      "label": "Halloween late opening · Halloween Trail"
                    },
                    {
                      "date": "2026-10-31",
                      "status": "open",
                      "opens": "10:00",
                      "closes": "21:00",
                      "lastEntry": "19:30",
                      "label": "Halloween late opening · Halloween Trail · Halloween Fireworks"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "BAD_REQUEST: Something in the request is wrong; the message says what, in words a guest can read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/attractions": {
      "get": {
        "operationId": "attractions",
        "summary": "Attractions",
        "description": "Every published attraction guests can see, in the park's order. Unlisted ones are left out.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string",
                            "description": "The entry's part of its address, as in /explore/attractions/{slug}. Changes when the park renames it: see GET /redirects."
                          },
                          "name": {
                            "type": "string"
                          },
                          "summary": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "One or two plain sentences for cards and lists."
                          },
                          "type": {
                            "type": "string",
                            "enum": [
                              "ride",
                              "roller_coaster",
                              "flat_ride",
                              "show",
                              "animal",
                              "exhibit",
                              "play",
                              "walk",
                              "trail",
                              "other"
                            ],
                            "description": "What kind of attraction it is; walk and trail have a distance and terrain."
                          },
                          "typeLabel": {
                            "type": "string",
                            "description": "The type in words (\"Rollercoaster\")."
                          },
                          "status": {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string",
                                "enum": [
                                  "operating",
                                  "closed",
                                  "seasonal",
                                  "coming_soon",
                                  "refurbishment",
                                  "unlisted"
                                ],
                                "description": "Whether guests can use it now. Unlisted entries are left out of lists but still have their own page."
                              },
                              "label": {
                                "type": "string",
                                "description": "The code in words for guests: \"Available\", \"Unavailable\", \"Seasonal\"…"
                              },
                              "note": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The park's note for guests, or a plain reason, while it is not available."
                              }
                            },
                            "required": [
                              "code",
                              "label",
                              "note"
                            ],
                            "additionalProperties": false
                          },
                          "isNew": {
                            "type": "boolean",
                            "description": "Show a \"New\" badge."
                          },
                          "height": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "minCm": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Riders must be at least this tall."
                              },
                              "maxCm": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Riders must be no taller than this."
                              },
                              "label": {
                                "type": "string",
                                "description": "Both limits in words: \"110 cm and over\", \"Up to 130 cm\", \"100 to 140 cm\"."
                              }
                            },
                            "required": [
                              "minCm",
                              "maxCm",
                              "label"
                            ],
                            "additionalProperties": false,
                            "description": "Height limits, inclusive; null when there are none."
                          },
                          "minAge": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "The youngest age allowed, in years, when the park set one."
                          },
                          "photo": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "url": {
                                "type": "string",
                                "description": "The full-size file."
                              },
                              "width": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                              },
                              "height": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known."
                              },
                              "alt": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Null means decorative: give it alt=\"\"."
                              },
                              "srcset": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "For an <img srcset>, when the file can be served at smaller sizes."
                              }
                            },
                            "required": [
                              "url",
                              "width",
                              "height",
                              "alt",
                              "srcset"
                            ],
                            "additionalProperties": false,
                            "description": "The first photo: the one for cards and lists."
                          },
                          "updatedAt": {
                            "type": "string",
                            "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                          }
                        },
                        "required": [
                          "slug",
                          "name",
                          "summary",
                          "type",
                          "typeLabel",
                          "status",
                          "isNew",
                          "height",
                          "minAge",
                          "photo",
                          "updatedAt"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "items"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "items": [
                    {
                      "slug": "halloween-trail",
                      "name": "Halloween Trail",
                      "summary": "A lantern-lit family trail through Pioneer Woods, on for Halloween.",
                      "type": "trail",
                      "typeLabel": "Trail",
                      "status": {
                        "code": "operating",
                        "label": "Available",
                        "note": null
                      },
                      "isNew": true,
                      "height": null,
                      "minAge": null,
                      "photo": null,
                      "updatedAt": "2026-10-20T18:57:33.077Z"
                    },
                    {
                      "slug": "iron-fall",
                      "name": "Iron Fall",
                      "summary": "Our new for 2028 B&M Dive Coaster.",
                      "type": "ride",
                      "typeLabel": "Ride",
                      "status": {
                        "code": "coming_soon",
                        "label": "Coming soon",
                        "note": "Coming soon"
                      },
                      "isNew": false,
                      "height": {
                        "minCm": 140,
                        "maxCm": null,
                        "label": "140 cm and over"
                      },
                      "minAge": null,
                      "photo": {
                        "url": "https://media.parkstack.net/riverside/1787448412512-b7kk9w.webp",
                        "width": 1872,
                        "height": 1056,
                        "alt": null,
                        "srcset": "https://media.parkstack.net/riverside/1787448412512-b7kk9w.webp?w=96 96w, https://media.parkstack.net/riverside/1787448412512-b7kk9w.webp?w=192 192w, https://media.parkstack.net/riverside/1787448412512-b7kk9w.webp?w=384 384w, https://med…"
                      },
                      "updatedAt": "2026-08-23T01:27:09.817Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/attractions/{slug}": {
      "get": {
        "operationId": "attraction",
        "summary": "One attraction",
        "description": "An attraction's full details, photos, accessibility and the events held there.\n\nOn NOT_FOUND, ask `/redirects?path=` before showing your own 404: the park may have renamed it.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The attraction's web address name, from its list.",
            "schema": {
              "type": "string"
            },
            "example": "halloween-trail"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string",
                      "description": "The entry's part of its address, as in /explore/attractions/{slug}. Changes when the park renames it: see GET /redirects."
                    },
                    "name": {
                      "type": "string"
                    },
                    "summary": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "One or two plain sentences for cards and lists."
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "ride",
                        "roller_coaster",
                        "flat_ride",
                        "show",
                        "animal",
                        "exhibit",
                        "play",
                        "walk",
                        "trail",
                        "other"
                      ],
                      "description": "What kind of attraction it is; walk and trail have a distance and terrain."
                    },
                    "typeLabel": {
                      "type": "string",
                      "description": "The type in words (\"Rollercoaster\")."
                    },
                    "status": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "operating",
                            "closed",
                            "seasonal",
                            "coming_soon",
                            "refurbishment",
                            "unlisted"
                          ],
                          "description": "Whether guests can use it now. Unlisted entries are left out of lists but still have their own page."
                        },
                        "label": {
                          "type": "string",
                          "description": "The code in words for guests: \"Available\", \"Unavailable\", \"Seasonal\"…"
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The park's note for guests, or a plain reason, while it is not available."
                        }
                      },
                      "required": [
                        "code",
                        "label",
                        "note"
                      ],
                      "additionalProperties": false
                    },
                    "isNew": {
                      "type": "boolean",
                      "description": "Show a \"New\" badge."
                    },
                    "height": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "minCm": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Riders must be at least this tall."
                        },
                        "maxCm": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Riders must be no taller than this."
                        },
                        "label": {
                          "type": "string",
                          "description": "Both limits in words: \"110 cm and over\", \"Up to 130 cm\", \"100 to 140 cm\"."
                        }
                      },
                      "required": [
                        "minCm",
                        "maxCm",
                        "label"
                      ],
                      "additionalProperties": false,
                      "description": "Height limits, inclusive; null when there are none."
                    },
                    "minAge": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "The youngest age allowed, in years, when the park set one."
                    },
                    "photo": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "url": {
                          "type": "string",
                          "description": "The full-size file."
                        },
                        "width": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                        },
                        "height": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known."
                        },
                        "alt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Null means decorative: give it alt=\"\"."
                        },
                        "srcset": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "For an <img srcset>, when the file can be served at smaller sizes."
                        }
                      },
                      "required": [
                        "url",
                        "width",
                        "height",
                        "alt",
                        "srcset"
                      ],
                      "additionalProperties": false,
                      "description": "The first photo: the one for cards and lists."
                    },
                    "updatedAt": {
                      "type": "string",
                      "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The full description as Markdown: bold, links and bulleted lists only."
                    },
                    "descriptionText": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The same description as plain words, for search results and meta tags."
                    },
                    "photos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "url": {
                            "type": "string",
                            "description": "The full-size file."
                          },
                          "width": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                          },
                          "height": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "In pixels, when known."
                          },
                          "alt": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Null means decorative: give it alt=\"\"."
                          },
                          "srcset": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "For an <img srcset>, when the file can be served at smaller sizes."
                          }
                        },
                        "required": [
                          "url",
                          "width",
                          "height",
                          "alt",
                          "srcset"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "thrillLevel": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "1 (gentle) to 5 (most intense), when the park set one."
                    },
                    "durationMinutes": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "How long it lasts or takes."
                    },
                    "distanceMetres": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "A walk or trail's length."
                    },
                    "terrain": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "easy",
                        "moderate",
                        "challenging"
                      ],
                      "description": "How hard a walk or trail's ground is."
                    },
                    "wheelchairAccess": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "full",
                            "partial",
                            "none"
                          ]
                        },
                        "label": {
                          "type": "string",
                          "description": "The code in words for guests."
                        }
                      },
                      "required": [
                        "code",
                        "label"
                      ],
                      "additionalProperties": false,
                      "description": "Null when the park has not said."
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "when": {
                            "type": "string",
                            "description": "When it is on, in words."
                          }
                        },
                        "required": [
                          "slug",
                          "name",
                          "when"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Published events held here that have not ended."
                    },
                    "seo": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "description": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "title",
                        "description"
                      ],
                      "additionalProperties": false,
                      "description": "The park's own wording for search results and link previews; when null, use the name and summary."
                    }
                  },
                  "required": [
                    "slug",
                    "name",
                    "summary",
                    "type",
                    "typeLabel",
                    "status",
                    "isNew",
                    "height",
                    "minAge",
                    "photo",
                    "updatedAt",
                    "description",
                    "descriptionText",
                    "photos",
                    "thrillLevel",
                    "durationMinutes",
                    "distanceMetres",
                    "terrain",
                    "wheelchairAccess",
                    "events",
                    "seo"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "slug": "halloween-trail",
                  "name": "Halloween Trail",
                  "summary": "A lantern-lit family trail through Pioneer Woods, on for Halloween.",
                  "type": "trail",
                  "typeLabel": "Trail",
                  "status": {
                    "code": "operating",
                    "label": "Available",
                    "note": null
                  },
                  "isNew": true,
                  "height": null,
                  "minAge": null,
                  "photo": null,
                  "updatedAt": "2026-10-20T18:57:33.077Z",
                  "description": "Follow the lanterns through Pioneer Woods and collect stamps at six spooky stops, then swap your card for a treat bag at the end.\n\nThe daytime path has mild frights and no jump scares, so it suits younger explorers. It runs on weekends t…",
                  "descriptionText": "Follow the lanterns through Pioneer Woods and collect stamps at six spooky stops, then swap your card for a treat bag at the end. The daytime path has mild frights and no jump scares, so it suits younger explorers. It runs on weekends th…",
                  "photos": [],
                  "thrillLevel": null,
                  "durationMinutes": null,
                  "distanceMetres": null,
                  "terrain": null,
                  "wheelchairAccess": null,
                  "events": [
                    {
                      "slug": "halloween-trail",
                      "name": "Halloween Trail",
                      "when": "Weekends, 3 October to 1 November, plus 26 to 30 October, during opening hours"
                    }
                  ],
                  "seo": {
                    "title": null,
                    "description": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: Nothing published at that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/food-and-drink": {
      "get": {
        "operationId": "foodAndDrink",
        "summary": "Food and drink",
        "description": "Restaurants, cafés, kiosks and bars, with dietary options and price bands.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string",
                            "description": "The entry's part of its address, as in /explore/attractions/{slug}. Changes when the park renames it: see GET /redirects."
                          },
                          "name": {
                            "type": "string"
                          },
                          "summary": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "One or two plain sentences for cards and lists."
                          },
                          "type": {
                            "type": "string",
                            "enum": [
                              "restaurant",
                              "cafe",
                              "kiosk",
                              "bar",
                              "food_truck",
                              "other"
                            ],
                            "description": "What kind of place it is."
                          },
                          "typeLabel": {
                            "type": "string",
                            "description": "The type in words (\"Café\")."
                          },
                          "status": {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string",
                                "enum": [
                                  "operating",
                                  "closed",
                                  "seasonal",
                                  "coming_soon",
                                  "refurbishment",
                                  "unlisted"
                                ],
                                "description": "Whether guests can use it now. Unlisted entries are left out of lists but still have their own page."
                              },
                              "label": {
                                "type": "string",
                                "description": "The code in words for guests: \"Available\", \"Unavailable\", \"Seasonal\"…"
                              },
                              "note": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The park's note for guests, or a plain reason, while it is not available."
                              }
                            },
                            "required": [
                              "code",
                              "label",
                              "note"
                            ],
                            "additionalProperties": false
                          },
                          "cuisine": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The park's words (\"Pizza and pasta\")."
                          },
                          "location": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Where it is in the park, in the park's words (\"Next to the main entrance\")."
                          },
                          "priceBand": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "1 to 4: show as £ to ££££."
                          },
                          "mealPeriods": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The meals it serves, in the park's words (\"Lunch and snacks\")."
                          },
                          "hoursNote": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The park's note on when it is open (\"Weekends only\"), shown as written."
                          },
                          "dietary": {
                            "type": "object",
                            "properties": {
                              "vegetarian": {
                                "type": "boolean"
                              },
                              "vegan": {
                                "type": "boolean"
                              },
                              "glutenFree": {
                                "type": "boolean"
                              }
                            },
                            "required": [
                              "vegetarian",
                              "vegan",
                              "glutenFree"
                            ],
                            "additionalProperties": false,
                            "description": "Options it offers; false means the park has not said it does."
                          },
                          "reservations": {
                            "type": "boolean",
                            "description": "Whether guests can book a table."
                          },
                          "servesAlcohol": {
                            "type": "boolean"
                          },
                          "photo": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "url": {
                                "type": "string",
                                "description": "The full-size file."
                              },
                              "width": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                              },
                              "height": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known."
                              },
                              "alt": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Null means decorative: give it alt=\"\"."
                              },
                              "srcset": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "For an <img srcset>, when the file can be served at smaller sizes."
                              }
                            },
                            "required": [
                              "url",
                              "width",
                              "height",
                              "alt",
                              "srcset"
                            ],
                            "additionalProperties": false,
                            "description": "The first photo: the one for cards and lists."
                          },
                          "updatedAt": {
                            "type": "string",
                            "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                          }
                        },
                        "required": [
                          "slug",
                          "name",
                          "summary",
                          "type",
                          "typeLabel",
                          "status",
                          "cuisine",
                          "location",
                          "priceBand",
                          "mealPeriods",
                          "hoursNote",
                          "dietary",
                          "reservations",
                          "servesAlcohol",
                          "photo",
                          "updatedAt"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "items"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "items": [
                    {
                      "slug": "the-mill-restaurant",
                      "name": "The Mill Restaurant",
                      "summary": "Table-service British cooking in the old mill, with a terrace over the race.",
                      "type": "restaurant",
                      "typeLabel": "Restaurant",
                      "status": {
                        "code": "operating",
                        "label": "Available",
                        "note": null
                      },
                      "cuisine": "British",
                      "location": "Mill courtyard",
                      "priceBand": 3,
                      "mealPeriods": "Lunch, dinner",
                      "hoursNote": "12:00–15:00 and 17:00–20:00",
                      "dietary": {
                        "vegetarian": true,
                        "vegan": true,
                        "glutenFree": true
                      },
                      "reservations": true,
                      "servesAlcohol": true,
                      "photo": {
                        "url": "https://media.parkstack.net/riverside/1787457896122-e66mt0.webp",
                        "width": 1408,
                        "height": 768,
                        "alt": null,
                        "srcset": "https://media.parkstack.net/riverside/1787457896122-e66mt0.webp?w=96 96w, https://media.parkstack.net/riverside/1787457896122-e66mt0.webp?w=192 192w, https://media.parkstack.net/riverside/1787457896122-e66mt0.webp?w=384 384w, https://med…"
                      },
                      "updatedAt": "2026-08-23T04:04:59.224Z"
                    },
                    {
                      "slug": "rapids-cafe",
                      "name": "Rapids Café",
                      "summary": "Counter-service café next to the water ride. Jacket potatoes, toasties and cake.",
                      "type": "cafe",
                      "typeLabel": "Café",
                      "status": {
                        "code": "operating",
                        "label": "Available",
                        "note": null
                      },
                      "cuisine": "Café",
                      "location": "River Rapids",
                      "priceBand": 2,
                      "mealPeriods": "Breakfast, lunch, snacks",
                      "hoursNote": "Opens with the park; kitchen until one hour before close",
                      "dietary": {
                        "vegetarian": true,
                        "vegan": true,
                        "glutenFree": true
                      },
                      "reservations": false,
                      "servesAlcohol": true,
                      "photo": {
                        "url": "https://media.parkstack.net/riverside/1790631494156-2n0am3.webp",
                        "width": 1600,
                        "height": 1200,
                        "alt": null,
                        "srcset": "https://media.parkstack.net/riverside/1790631494156-2n0am3.webp?w=96 96w, https://media.parkstack.net/riverside/1790631494156-2n0am3.webp?w=192 192w, https://media.parkstack.net/riverside/1790631494156-2n0am3.webp?w=384 384w, https://med…"
                      },
                      "updatedAt": "2026-09-28T21:38:57.050Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/food-and-drink/{slug}": {
      "get": {
        "operationId": "foodPlace",
        "summary": "One food or drink place",
        "description": "A food or drink place's full details and photos.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The food or drink place's web address name, from its list.",
            "schema": {
              "type": "string"
            },
            "example": "the-mill-restaurant"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string",
                      "description": "The entry's part of its address, as in /explore/attractions/{slug}. Changes when the park renames it: see GET /redirects."
                    },
                    "name": {
                      "type": "string"
                    },
                    "summary": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "One or two plain sentences for cards and lists."
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "restaurant",
                        "cafe",
                        "kiosk",
                        "bar",
                        "food_truck",
                        "other"
                      ],
                      "description": "What kind of place it is."
                    },
                    "typeLabel": {
                      "type": "string",
                      "description": "The type in words (\"Café\")."
                    },
                    "status": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "operating",
                            "closed",
                            "seasonal",
                            "coming_soon",
                            "refurbishment",
                            "unlisted"
                          ],
                          "description": "Whether guests can use it now. Unlisted entries are left out of lists but still have their own page."
                        },
                        "label": {
                          "type": "string",
                          "description": "The code in words for guests: \"Available\", \"Unavailable\", \"Seasonal\"…"
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The park's note for guests, or a plain reason, while it is not available."
                        }
                      },
                      "required": [
                        "code",
                        "label",
                        "note"
                      ],
                      "additionalProperties": false
                    },
                    "cuisine": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The park's words (\"Pizza and pasta\")."
                    },
                    "location": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Where it is in the park, in the park's words (\"Next to the main entrance\")."
                    },
                    "priceBand": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "1 to 4: show as £ to ££££."
                    },
                    "mealPeriods": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The meals it serves, in the park's words (\"Lunch and snacks\")."
                    },
                    "hoursNote": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The park's note on when it is open (\"Weekends only\"), shown as written."
                    },
                    "dietary": {
                      "type": "object",
                      "properties": {
                        "vegetarian": {
                          "type": "boolean"
                        },
                        "vegan": {
                          "type": "boolean"
                        },
                        "glutenFree": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "vegetarian",
                        "vegan",
                        "glutenFree"
                      ],
                      "additionalProperties": false,
                      "description": "Options it offers; false means the park has not said it does."
                    },
                    "reservations": {
                      "type": "boolean",
                      "description": "Whether guests can book a table."
                    },
                    "servesAlcohol": {
                      "type": "boolean"
                    },
                    "photo": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "url": {
                          "type": "string",
                          "description": "The full-size file."
                        },
                        "width": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                        },
                        "height": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known."
                        },
                        "alt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Null means decorative: give it alt=\"\"."
                        },
                        "srcset": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "For an <img srcset>, when the file can be served at smaller sizes."
                        }
                      },
                      "required": [
                        "url",
                        "width",
                        "height",
                        "alt",
                        "srcset"
                      ],
                      "additionalProperties": false,
                      "description": "The first photo: the one for cards and lists."
                    },
                    "updatedAt": {
                      "type": "string",
                      "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The full description as Markdown: bold, links and bulleted lists only."
                    },
                    "descriptionText": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The same description as plain words, for search results and meta tags."
                    },
                    "photos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "url": {
                            "type": "string",
                            "description": "The full-size file."
                          },
                          "width": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                          },
                          "height": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "In pixels, when known."
                          },
                          "alt": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Null means decorative: give it alt=\"\"."
                          },
                          "srcset": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "For an <img srcset>, when the file can be served at smaller sizes."
                          }
                        },
                        "required": [
                          "url",
                          "width",
                          "height",
                          "alt",
                          "srcset"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "seo": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "description": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "title",
                        "description"
                      ],
                      "additionalProperties": false,
                      "description": "The park's own wording for search results and link previews; when null, use the name and summary."
                    }
                  },
                  "required": [
                    "slug",
                    "name",
                    "summary",
                    "type",
                    "typeLabel",
                    "status",
                    "cuisine",
                    "location",
                    "priceBand",
                    "mealPeriods",
                    "hoursNote",
                    "dietary",
                    "reservations",
                    "servesAlcohol",
                    "photo",
                    "updatedAt",
                    "description",
                    "descriptionText",
                    "photos",
                    "seo"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "slug": "the-mill-restaurant",
                  "name": "The Mill Restaurant",
                  "summary": "Table-service British cooking in the old mill, with a terrace over the race.",
                  "type": "restaurant",
                  "typeLabel": "Restaurant",
                  "status": {
                    "code": "operating",
                    "label": "Available",
                    "note": null
                  },
                  "cuisine": "British",
                  "location": "Mill courtyard",
                  "priceBand": 3,
                  "mealPeriods": "Lunch, dinner",
                  "hoursNote": "12:00–15:00 and 17:00–20:00",
                  "dietary": {
                    "vegetarian": true,
                    "vegan": true,
                    "glutenFree": true
                  },
                  "reservations": true,
                  "servesAlcohol": true,
                  "photo": {
                    "url": "https://media.parkstack.net/riverside/1787457896122-e66mt0.webp",
                    "width": 1408,
                    "height": 768,
                    "alt": null,
                    "srcset": "https://media.parkstack.net/riverside/1787457896122-e66mt0.webp?w=96 96w, https://media.parkstack.net/riverside/1787457896122-e66mt0.webp?w=192 192w, https://media.parkstack.net/riverside/1787457896122-e66mt0.webp?w=384 384w, https://med…"
                  },
                  "updatedAt": "2026-08-23T04:04:59.224Z",
                  "description": "A sit-down restaurant for longer meals. Sunday roast in season, children's portions, and a short wine list.\n\nReservations recommended after 17:00 on Fridays and Saturdays.",
                  "descriptionText": "A sit-down restaurant for longer meals. Sunday roast in season, children's portions, and a short wine list. Reservations recommended after 17:00 on Fridays and Saturdays.",
                  "photos": [
                    {
                      "url": "https://media.parkstack.net/riverside/1787457896122-e66mt0.webp",
                      "width": 1408,
                      "height": 768,
                      "alt": null,
                      "srcset": "https://media.parkstack.net/riverside/1787457896122-e66mt0.webp?w=96 96w, https://media.parkstack.net/riverside/1787457896122-e66mt0.webp?w=192 192w, https://media.parkstack.net/riverside/1787457896122-e66mt0.webp?w=384 384w, https://med…"
                    }
                  ],
                  "seo": {
                    "title": null,
                    "description": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: Nothing published at that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/shops-and-facilities": {
      "get": {
        "operationId": "shopsAndFacilities",
        "summary": "Shops and facilities",
        "description": "Shops, toilets, first aid, lockers and the like. `isShop` separates shops from the rest.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string",
                            "description": "The entry's part of its address, as in /explore/attractions/{slug}. Changes when the park renames it: see GET /redirects."
                          },
                          "name": {
                            "type": "string"
                          },
                          "summary": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "One or two plain sentences for cards and lists."
                          },
                          "type": {
                            "type": "string",
                            "enum": [
                              "shop",
                              "toilets",
                              "accessible_toilets",
                              "baby_change",
                              "first_aid",
                              "guest_services",
                              "lockers",
                              "cash_machine",
                              "picnic_area",
                              "other"
                            ],
                            "description": "What kind of shop or facility it is."
                          },
                          "typeLabel": {
                            "type": "string",
                            "description": "The type in words (\"Baby changing\")."
                          },
                          "isShop": {
                            "type": "boolean",
                            "description": "Shops are listed apart from other facilities on our site."
                          },
                          "status": {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string",
                                "enum": [
                                  "operating",
                                  "closed",
                                  "seasonal",
                                  "coming_soon",
                                  "refurbishment",
                                  "unlisted"
                                ],
                                "description": "Whether guests can use it now. Unlisted entries are left out of lists but still have their own page."
                              },
                              "label": {
                                "type": "string",
                                "description": "The code in words for guests: \"Available\", \"Unavailable\", \"Seasonal\"…"
                              },
                              "note": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The park's note for guests, or a plain reason, while it is not available."
                              }
                            },
                            "required": [
                              "code",
                              "label",
                              "note"
                            ],
                            "additionalProperties": false
                          },
                          "location": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Where it is in the park, in the park's words (\"Next to the main entrance\")."
                          },
                          "hoursNote": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The park's note on when it is open (\"Weekends only\"), shown as written."
                          },
                          "photo": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "url": {
                                "type": "string",
                                "description": "The full-size file."
                              },
                              "width": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                              },
                              "height": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known."
                              },
                              "alt": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Null means decorative: give it alt=\"\"."
                              },
                              "srcset": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "For an <img srcset>, when the file can be served at smaller sizes."
                              }
                            },
                            "required": [
                              "url",
                              "width",
                              "height",
                              "alt",
                              "srcset"
                            ],
                            "additionalProperties": false,
                            "description": "The first photo: the one for cards and lists."
                          },
                          "updatedAt": {
                            "type": "string",
                            "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                          }
                        },
                        "required": [
                          "slug",
                          "name",
                          "summary",
                          "type",
                          "typeLabel",
                          "isShop",
                          "status",
                          "location",
                          "hoursNote",
                          "photo",
                          "updatedAt"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "items"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "items": [
                    {
                      "slug": "first-aid",
                      "name": "First aid",
                      "summary": "Trained first aiders, open whenever the park is.",
                      "type": "first_aid",
                      "typeLabel": "First aid",
                      "isShop": false,
                      "status": {
                        "code": "operating",
                        "label": "Available",
                        "note": null
                      },
                      "location": "Next to Guest Services, by the main entrance",
                      "hoursNote": "Open whenever the park is",
                      "photo": null,
                      "updatedAt": "2026-10-20T09:12:44.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/shops-and-facilities/{slug}": {
      "get": {
        "operationId": "facility",
        "summary": "One shop or facility",
        "description": "A shop or facility's full details and photos.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The shop or facility's web address name, from its list.",
            "schema": {
              "type": "string"
            },
            "example": "first-aid"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string",
                      "description": "The entry's part of its address, as in /explore/attractions/{slug}. Changes when the park renames it: see GET /redirects."
                    },
                    "name": {
                      "type": "string"
                    },
                    "summary": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "One or two plain sentences for cards and lists."
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "shop",
                        "toilets",
                        "accessible_toilets",
                        "baby_change",
                        "first_aid",
                        "guest_services",
                        "lockers",
                        "cash_machine",
                        "picnic_area",
                        "other"
                      ],
                      "description": "What kind of shop or facility it is."
                    },
                    "typeLabel": {
                      "type": "string",
                      "description": "The type in words (\"Baby changing\")."
                    },
                    "isShop": {
                      "type": "boolean",
                      "description": "Shops are listed apart from other facilities on our site."
                    },
                    "status": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "operating",
                            "closed",
                            "seasonal",
                            "coming_soon",
                            "refurbishment",
                            "unlisted"
                          ],
                          "description": "Whether guests can use it now. Unlisted entries are left out of lists but still have their own page."
                        },
                        "label": {
                          "type": "string",
                          "description": "The code in words for guests: \"Available\", \"Unavailable\", \"Seasonal\"…"
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The park's note for guests, or a plain reason, while it is not available."
                        }
                      },
                      "required": [
                        "code",
                        "label",
                        "note"
                      ],
                      "additionalProperties": false
                    },
                    "location": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Where it is in the park, in the park's words (\"Next to the main entrance\")."
                    },
                    "hoursNote": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The park's note on when it is open (\"Weekends only\"), shown as written."
                    },
                    "photo": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "url": {
                          "type": "string",
                          "description": "The full-size file."
                        },
                        "width": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                        },
                        "height": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known."
                        },
                        "alt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Null means decorative: give it alt=\"\"."
                        },
                        "srcset": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "For an <img srcset>, when the file can be served at smaller sizes."
                        }
                      },
                      "required": [
                        "url",
                        "width",
                        "height",
                        "alt",
                        "srcset"
                      ],
                      "additionalProperties": false,
                      "description": "The first photo: the one for cards and lists."
                    },
                    "updatedAt": {
                      "type": "string",
                      "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The full description as Markdown: bold, links and bulleted lists only."
                    },
                    "descriptionText": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The same description as plain words, for search results and meta tags."
                    },
                    "photos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "url": {
                            "type": "string",
                            "description": "The full-size file."
                          },
                          "width": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                          },
                          "height": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "In pixels, when known."
                          },
                          "alt": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Null means decorative: give it alt=\"\"."
                          },
                          "srcset": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "For an <img srcset>, when the file can be served at smaller sizes."
                          }
                        },
                        "required": [
                          "url",
                          "width",
                          "height",
                          "alt",
                          "srcset"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "seo": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "description": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "title",
                        "description"
                      ],
                      "additionalProperties": false,
                      "description": "The park's own wording for search results and link previews; when null, use the name and summary."
                    }
                  },
                  "required": [
                    "slug",
                    "name",
                    "summary",
                    "type",
                    "typeLabel",
                    "isShop",
                    "status",
                    "location",
                    "hoursNote",
                    "photo",
                    "updatedAt",
                    "description",
                    "descriptionText",
                    "photos",
                    "seo"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "slug": "first-aid",
                  "name": "First aid",
                  "summary": "Trained first aiders, open whenever the park is.",
                  "type": "first_aid",
                  "typeLabel": "First aid",
                  "isShop": false,
                  "status": {
                    "code": "operating",
                    "label": "Available",
                    "note": null
                  },
                  "location": "Next to Guest Services, by the main entrance",
                  "hoursNote": "Open whenever the park is",
                  "photo": null,
                  "updatedAt": "2026-10-20T09:12:44.000Z",
                  "description": "Our first aid team can help with **cuts, bumps and anything else**. Ask any member of staff and they will call them.",
                  "descriptionText": "Our first aid team can help with cuts, bumps and anything else. Ask any member of staff and they will call them.",
                  "photos": [],
                  "seo": {
                    "title": null,
                    "description": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: Nothing published at that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/events": {
      "get": {
        "operationId": "events",
        "summary": "Events",
        "description": "Current and upcoming events first, then past ones newest first (`past: true`), with when they are on in words.\n\nShow past events apart, as an archive, or leave them out.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string",
                            "description": "The entry's part of its address, as in /explore/attractions/{slug}. Changes when the park renames it: see GET /redirects."
                          },
                          "name": {
                            "type": "string"
                          },
                          "summary": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "One or two plain sentences for cards and lists."
                          },
                          "when": {
                            "type": "string",
                            "description": "\"Weekends, 3 October to 1 November, during opening hours\": on the park's clock."
                          },
                          "startsAt": {
                            "type": "string",
                            "description": "First start and last end, as ISO times."
                          },
                          "endsAt": {
                            "type": "string"
                          },
                          "nextStart": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The next start (a showtime, or the day's opening), or null once it is over."
                          },
                          "past": {
                            "type": "boolean",
                            "description": "Over: our site shows these in a separate archive, newest first."
                          },
                          "location": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Where it is in the park, in the park's words (\"Next to the main entrance\")."
                          },
                          "attraction": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "slug": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "slug",
                              "name"
                            ],
                            "additionalProperties": false,
                            "description": "The attraction it happens at, when the park chose one."
                          },
                          "photo": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "url": {
                                "type": "string",
                                "description": "The full-size file."
                              },
                              "width": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                              },
                              "height": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known."
                              },
                              "alt": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Null means decorative: give it alt=\"\"."
                              },
                              "srcset": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "For an <img srcset>, when the file can be served at smaller sizes."
                              }
                            },
                            "required": [
                              "url",
                              "width",
                              "height",
                              "alt",
                              "srcset"
                            ],
                            "additionalProperties": false,
                            "description": "The first photo: the one for cards and lists."
                          },
                          "updatedAt": {
                            "type": "string",
                            "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                          }
                        },
                        "required": [
                          "slug",
                          "name",
                          "summary",
                          "when",
                          "startsAt",
                          "endsAt",
                          "nextStart",
                          "past",
                          "location",
                          "attraction",
                          "photo",
                          "updatedAt"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "items"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "items": [
                    {
                      "slug": "halloween-trail",
                      "name": "Halloween Trail",
                      "summary": "A family scare trail through the woods. Mild frights, no jump scares on the daytime path.",
                      "when": "Weekends, 3 October to 1 November, plus 26 to 30 October, during opening hours",
                      "startsAt": "2026-10-02T23:00:00.000Z",
                      "endsAt": "2026-11-01T23:59:00.000Z",
                      "nextStart": "2026-10-30T10:00:00.000Z",
                      "past": false,
                      "location": "Pioneer Woods",
                      "attraction": {
                        "name": "Halloween Trail",
                        "slug": "halloween-trail"
                      },
                      "photo": null,
                      "updatedAt": "2026-10-20T21:06:58.561Z"
                    },
                    {
                      "slug": "halloween-fireworks",
                      "name": "Halloween Fireworks",
                      "summary": "A Halloween fireworks display over the lake to close the season.",
                      "when": "Saturday 31 October at 20:00",
                      "startsAt": "2026-10-31T20:00:00.000Z",
                      "endsAt": "2026-10-31T21:00:00.000Z",
                      "nextStart": "2026-10-31T20:00:00.000Z",
                      "past": false,
                      "location": "Lakeside lawn",
                      "attraction": null,
                      "photo": null,
                      "updatedAt": "2026-10-20T21:07:08.197Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/events/{slug}": {
      "get": {
        "operationId": "event",
        "summary": "One event",
        "description": "An event's full details, every date it is on and its showtimes.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The event's web address name, from its list.",
            "schema": {
              "type": "string"
            },
            "example": "halloween-trail"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string",
                      "description": "The entry's part of its address, as in /explore/attractions/{slug}. Changes when the park renames it: see GET /redirects."
                    },
                    "name": {
                      "type": "string"
                    },
                    "summary": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "One or two plain sentences for cards and lists."
                    },
                    "when": {
                      "type": "string",
                      "description": "\"Weekends, 3 October to 1 November, during opening hours\": on the park's clock."
                    },
                    "startsAt": {
                      "type": "string",
                      "description": "First start and last end, as ISO times."
                    },
                    "endsAt": {
                      "type": "string"
                    },
                    "nextStart": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The next start (a showtime, or the day's opening), or null once it is over."
                    },
                    "past": {
                      "type": "boolean",
                      "description": "Over: our site shows these in a separate archive, newest first."
                    },
                    "location": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Where it is in the park, in the park's words (\"Next to the main entrance\")."
                    },
                    "attraction": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "slug": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "slug",
                        "name"
                      ],
                      "additionalProperties": false,
                      "description": "The attraction it happens at, when the park chose one."
                    },
                    "photo": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "url": {
                          "type": "string",
                          "description": "The full-size file."
                        },
                        "width": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                        },
                        "height": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "In pixels, when known."
                        },
                        "alt": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Null means decorative: give it alt=\"\"."
                        },
                        "srcset": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "For an <img srcset>, when the file can be served at smaller sizes."
                        }
                      },
                      "required": [
                        "url",
                        "width",
                        "height",
                        "alt",
                        "srcset"
                      ],
                      "additionalProperties": false,
                      "description": "The first photo: the one for cards and lists."
                    },
                    "updatedAt": {
                      "type": "string",
                      "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The full description as Markdown: bold, links and bulleted lists only."
                    },
                    "descriptionText": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The same description as plain words, for search results and meta tags."
                    },
                    "photos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "url": {
                            "type": "string",
                            "description": "The full-size file."
                          },
                          "width": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                          },
                          "height": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "In pixels, when known."
                          },
                          "alt": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Null means decorative: give it alt=\"\"."
                          },
                          "srcset": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "For an <img srcset>, when the file can be served at smaller sizes."
                          }
                        },
                        "required": [
                          "url",
                          "width",
                          "height",
                          "alt",
                          "srcset"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "dates": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Every date it is on, YYYY-MM-DD, at most a year's worth."
                    },
                    "showtimes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Showtimes (HH:mm) when it has them, else empty."
                    },
                    "seo": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "description": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "title",
                        "description"
                      ],
                      "additionalProperties": false,
                      "description": "The park's own wording for search results and link previews; when null, use the name and summary."
                    }
                  },
                  "required": [
                    "slug",
                    "name",
                    "summary",
                    "when",
                    "startsAt",
                    "endsAt",
                    "nextStart",
                    "past",
                    "location",
                    "attraction",
                    "photo",
                    "updatedAt",
                    "description",
                    "descriptionText",
                    "photos",
                    "dates",
                    "showtimes",
                    "seo"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "slug": "halloween-trail",
                  "name": "Halloween Trail",
                  "summary": "A family scare trail through the woods. Mild frights, no jump scares on the daytime path.",
                  "when": "Weekends, 3 October to 1 November, plus 26 to 30 October, during opening hours",
                  "startsAt": "2026-10-02T23:00:00.000Z",
                  "endsAt": "2026-11-01T23:59:00.000Z",
                  "nextStart": "2026-10-30T10:00:00.000Z",
                  "past": false,
                  "location": "Pioneer Woods",
                  "attraction": {
                    "name": "Halloween Trail",
                    "slug": "halloween-trail"
                  },
                  "photo": null,
                  "updatedAt": "2026-10-20T21:06:58.561Z",
                  "description": "Collect stamps at six points and swap them for a treat bag. An after-dark trail for ages 12 and over runs from 18:30 on late-opening days, 24 October to 1 November.",
                  "descriptionText": "Collect stamps at six points and swap them for a treat bag. An after-dark trail for ages 12 and over runs from 18:30 on late-opening days, 24 October to 1 November.",
                  "photos": [],
                  "dates": [
                    "2026-10-03",
                    "2026-10-04"
                  ],
                  "showtimes": [],
                  "seo": {
                    "title": null,
                    "description": null
                  }
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: Nothing published at that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/faq": {
      "get": {
        "operationId": "faq",
        "summary": "Questions and answers",
        "description": "The park's FAQ in its groups and order.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "groups": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "heading": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "questions": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "question": {
                                  "type": "string"
                                },
                                "answer": {
                                  "type": "string",
                                  "description": "Markdown: bold, links and bulleted lists only."
                                },
                                "answerText": {
                                  "type": "string",
                                  "description": "The same answer as plain words."
                                }
                              },
                              "required": [
                                "question",
                                "answer",
                                "answerText"
                              ],
                              "additionalProperties": false
                            }
                          }
                        },
                        "required": [
                          "heading",
                          "questions"
                        ],
                        "additionalProperties": false
                      },
                      "description": "In the order guests see them; questions with no group come first, with a null heading."
                    }
                  },
                  "required": [
                    "groups"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "groups": [
                    {
                      "heading": "Visit",
                      "questions": [
                        {
                          "question": "What time do the gates open?",
                          "answer": "Most days we open at 10:00. Sunday is our planned closed day, except during school holidays. Always check opening hours before you travel.",
                          "answerText": "Most days we open at 10:00. Sunday is our planned closed day, except during school holidays. Always check opening hours before you travel."
                        }
                      ]
                    },
                    {
                      "heading": "Rides",
                      "questions": [
                        {
                          "question": "Do you have height restrictions?",
                          "answer": "Yes. Each ride page lists a minimum height. Thunder Run is 1.20 m and Sky Drop is 1.30 m. Measuring posts are at the entrance to every ride.",
                          "answerText": "Yes. Each ride page lists a minimum height. Thunder Run is 1.20 m and Sky Drop is 1.30 m. Measuring posts are at the entrance to every ride."
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/pages/{slug}": {
      "get": {
        "operationId": "page",
        "summary": "A page",
        "description": "A page's words, photos and files: about, accessibility, privacy, ticket-terms and the park's own pages.\n\nShow every entry in `notices` after the page's own words: Privacy and Ticket terms carry parts the law needs that the park does not write itself.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The page's web address name, such as about, privacy or ticket-terms.",
            "schema": {
              "type": "string"
            },
            "example": "ticket-terms"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string",
                      "description": "The page's address, as in /{slug} (\"about\", \"privacy\", \"ticket-terms\")."
                    },
                    "title": {
                      "type": "string"
                    },
                    "text": {
                      "type": "string",
                      "description": "The page's words as Markdown (blocks such as galleries and videos are left out in v1)."
                    },
                    "photos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "url": {
                            "type": "string",
                            "description": "The full-size file."
                          },
                          "width": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                          },
                          "height": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "In pixels, when known."
                          },
                          "alt": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Null means decorative: give it alt=\"\"."
                          },
                          "srcset": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "For an <img srcset>, when the file can be served at smaller sizes."
                          }
                        },
                        "required": [
                          "url",
                          "width",
                          "height",
                          "alt",
                          "srcset"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "files": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "url": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "mime": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "url",
                          "name",
                          "mime"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Files to download (PDFs and the like)."
                    },
                    "notices": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "heading": {
                            "type": "string"
                          },
                          "paragraphs": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "required": [
                          "heading",
                          "paragraphs"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Parts Parkstack adds that the page must show after its own words (Privacy and Ticket terms)."
                    },
                    "seo": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "description": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "title",
                        "description"
                      ],
                      "additionalProperties": false,
                      "description": "The park's own wording for search results and link previews; when null, use the name and summary."
                    },
                    "updatedAt": {
                      "type": "string",
                      "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                    }
                  },
                  "required": [
                    "slug",
                    "title",
                    "text",
                    "photos",
                    "files",
                    "notices",
                    "seo",
                    "updatedAt"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "slug": "about",
                  "title": "About Riverside",
                  "text": "Riverside Adventure Park opened in 1984 on the site of a Victorian mill. We are an independently run park on the edge of the river valley, with a mix of thrill rides, family coasters and quiet gardens.\n\nOur aim is simple: a clean, well-r…",
                  "photos": [],
                  "files": [],
                  "notices": [],
                  "seo": {
                    "title": "About Riverside",
                    "description": null
                  },
                  "updatedAt": "2026-08-22T23:38:35.161Z"
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: Nothing published at that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/closures": {
      "get": {
        "operationId": "closures",
        "summary": "Attraction closures",
        "description": "Attractions unavailable today, and those closed for the season or for refurbishment.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "shown": {
                      "type": "boolean",
                      "description": "Whether the park shows this list on its own Parkstack site (Attraction availability switched on)."
                    },
                    "temporary": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string",
                            "description": "The entry's part of its address, as in /explore/attractions/{slug}. Changes when the park renames it: see GET /redirects."
                          },
                          "name": {
                            "type": "string"
                          },
                          "summary": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "One or two plain sentences for cards and lists."
                          },
                          "type": {
                            "type": "string",
                            "enum": [
                              "ride",
                              "roller_coaster",
                              "flat_ride",
                              "show",
                              "animal",
                              "exhibit",
                              "play",
                              "walk",
                              "trail",
                              "other"
                            ],
                            "description": "What kind of attraction it is; walk and trail have a distance and terrain."
                          },
                          "typeLabel": {
                            "type": "string",
                            "description": "The type in words (\"Rollercoaster\")."
                          },
                          "status": {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string",
                                "enum": [
                                  "operating",
                                  "closed",
                                  "seasonal",
                                  "coming_soon",
                                  "refurbishment",
                                  "unlisted"
                                ],
                                "description": "Whether guests can use it now. Unlisted entries are left out of lists but still have their own page."
                              },
                              "label": {
                                "type": "string",
                                "description": "The code in words for guests: \"Available\", \"Unavailable\", \"Seasonal\"…"
                              },
                              "note": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The park's note for guests, or a plain reason, while it is not available."
                              }
                            },
                            "required": [
                              "code",
                              "label",
                              "note"
                            ],
                            "additionalProperties": false
                          },
                          "isNew": {
                            "type": "boolean",
                            "description": "Show a \"New\" badge."
                          },
                          "height": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "minCm": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Riders must be at least this tall."
                              },
                              "maxCm": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Riders must be no taller than this."
                              },
                              "label": {
                                "type": "string",
                                "description": "Both limits in words: \"110 cm and over\", \"Up to 130 cm\", \"100 to 140 cm\"."
                              }
                            },
                            "required": [
                              "minCm",
                              "maxCm",
                              "label"
                            ],
                            "additionalProperties": false,
                            "description": "Height limits, inclusive; null when there are none."
                          },
                          "minAge": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "The youngest age allowed, in years, when the park set one."
                          },
                          "photo": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "url": {
                                "type": "string",
                                "description": "The full-size file."
                              },
                              "width": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                              },
                              "height": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known."
                              },
                              "alt": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Null means decorative: give it alt=\"\"."
                              },
                              "srcset": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "For an <img srcset>, when the file can be served at smaller sizes."
                              }
                            },
                            "required": [
                              "url",
                              "width",
                              "height",
                              "alt",
                              "srcset"
                            ],
                            "additionalProperties": false,
                            "description": "The first photo: the one for cards and lists."
                          },
                          "updatedAt": {
                            "type": "string",
                            "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                          }
                        },
                        "required": [
                          "slug",
                          "name",
                          "summary",
                          "type",
                          "typeLabel",
                          "status",
                          "isNew",
                          "height",
                          "minAge",
                          "photo",
                          "updatedAt"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Unavailable now."
                    },
                    "scheduled": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string",
                            "description": "The entry's part of its address, as in /explore/attractions/{slug}. Changes when the park renames it: see GET /redirects."
                          },
                          "name": {
                            "type": "string"
                          },
                          "summary": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "One or two plain sentences for cards and lists."
                          },
                          "type": {
                            "type": "string",
                            "enum": [
                              "ride",
                              "roller_coaster",
                              "flat_ride",
                              "show",
                              "animal",
                              "exhibit",
                              "play",
                              "walk",
                              "trail",
                              "other"
                            ],
                            "description": "What kind of attraction it is; walk and trail have a distance and terrain."
                          },
                          "typeLabel": {
                            "type": "string",
                            "description": "The type in words (\"Rollercoaster\")."
                          },
                          "status": {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string",
                                "enum": [
                                  "operating",
                                  "closed",
                                  "seasonal",
                                  "coming_soon",
                                  "refurbishment",
                                  "unlisted"
                                ],
                                "description": "Whether guests can use it now. Unlisted entries are left out of lists but still have their own page."
                              },
                              "label": {
                                "type": "string",
                                "description": "The code in words for guests: \"Available\", \"Unavailable\", \"Seasonal\"…"
                              },
                              "note": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The park's note for guests, or a plain reason, while it is not available."
                              }
                            },
                            "required": [
                              "code",
                              "label",
                              "note"
                            ],
                            "additionalProperties": false
                          },
                          "isNew": {
                            "type": "boolean",
                            "description": "Show a \"New\" badge."
                          },
                          "height": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "minCm": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Riders must be at least this tall."
                              },
                              "maxCm": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Riders must be no taller than this."
                              },
                              "label": {
                                "type": "string",
                                "description": "Both limits in words: \"110 cm and over\", \"Up to 130 cm\", \"100 to 140 cm\"."
                              }
                            },
                            "required": [
                              "minCm",
                              "maxCm",
                              "label"
                            ],
                            "additionalProperties": false,
                            "description": "Height limits, inclusive; null when there are none."
                          },
                          "minAge": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "The youngest age allowed, in years, when the park set one."
                          },
                          "photo": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "url": {
                                "type": "string",
                                "description": "The full-size file."
                              },
                              "width": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                              },
                              "height": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known."
                              },
                              "alt": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Null means decorative: give it alt=\"\"."
                              },
                              "srcset": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "For an <img srcset>, when the file can be served at smaller sizes."
                              }
                            },
                            "required": [
                              "url",
                              "width",
                              "height",
                              "alt",
                              "srcset"
                            ],
                            "additionalProperties": false,
                            "description": "The first photo: the one for cards and lists."
                          },
                          "updatedAt": {
                            "type": "string",
                            "description": "When it last changed, as an ISO 8601 time in UTC. Useful for sitemaps and caches."
                          }
                        },
                        "required": [
                          "slug",
                          "name",
                          "summary",
                          "type",
                          "typeLabel",
                          "status",
                          "isNew",
                          "height",
                          "minAge",
                          "photo",
                          "updatedAt"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Seasonal or closed for refurbishment."
                    }
                  },
                  "required": [
                    "shown",
                    "temporary",
                    "scheduled"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "shown": true,
                  "temporary": [],
                  "scheduled": [
                    {
                      "slug": "sky-drop",
                      "name": "Sky Drop",
                      "summary": "A 40-metre drop tower currently closed for winter work.",
                      "type": "ride",
                      "typeLabel": "Ride",
                      "status": {
                        "code": "refurbishment",
                        "label": "Refurbishment",
                        "note": "Closed for refurbishment"
                      },
                      "isNew": false,
                      "height": {
                        "minCm": 130,
                        "maxCm": null,
                        "label": "130 cm and over"
                      },
                      "minAge": null,
                      "photo": {
                        "url": "https://media.parkstack.net/riverside/1790631023188-xeqohj.webp",
                        "width": 1600,
                        "height": 900,
                        "alt": null,
                        "srcset": "https://media.parkstack.net/riverside/1790631023188-xeqohj.webp?w=96 96w, https://media.parkstack.net/riverside/1790631023188-xeqohj.webp?w=192 192w, https://media.parkstack.net/riverside/1790631023188-xeqohj.webp?w=384 384w, https://med…"
                      },
                      "updatedAt": "2026-09-28T21:31:43.910Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/redirects": {
      "get": {
        "operationId": "redirects",
        "summary": "Old addresses",
        "description": "Where an old address now goes, after the park renamed or removed something.\n\nAsk with your own path. Both path and `to` are Parkstack guest-site addresses, such as /explore/attractions/{slug}; answer with a permanent redirect (308) to `to`.",
        "parameters": [
          {
            "name": "path",
            "in": "query",
            "required": true,
            "description": "The old path, starting with /.",
            "schema": {
              "type": "string"
            },
            "example": "/explore/attractions/old-name"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "to": {
                      "type": "string",
                      "description": "The path to send the guest to with a permanent redirect."
                    }
                  },
                  "required": [
                    "to"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "to": "/explore/attractions/dragon-flight"
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: Nothing published at that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tickets": {
      "get": {
        "operationId": "tickets",
        "summary": "Tickets",
        "description": "Tickets on sale online with their prices, VAT wording, days covered and re-entry.\n\nPrices are in pence and are the total the guest pays. Show them as they are; never add VAT or fees.\n\nShow `priceOnTheDayPence` only as what it is: the price for a dated ticket bought for today.\n\nWhile `onSaleOnline` is false, offer nothing to buy.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "onSaleOnline": {
                      "type": "boolean",
                      "description": "False: tickets are not on sale online now. Hide ticket links and offer nothing to buy."
                    },
                    "maxPerOrder": {
                      "type": "integer",
                      "description": "The most tickets one online order may hold, across every line."
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Send this in checkout lines."
                          },
                          "name": {
                            "type": "string"
                          },
                          "description": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Plain words (not Markdown)."
                          },
                          "photo": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "url": {
                                "type": "string",
                                "description": "The full-size file."
                              },
                              "width": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known: set width and height on the <img> so the page does not jump."
                              },
                              "height": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "In pixels, when known."
                              },
                              "alt": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Null means decorative: give it alt=\"\"."
                              },
                              "srcset": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "For an <img srcset>, when the file can be served at smaller sizes."
                              }
                            },
                            "required": [
                              "url",
                              "width",
                              "height",
                              "alt",
                              "srcset"
                            ],
                            "additionalProperties": false
                          },
                          "pricePence": {
                            "type": "integer",
                            "description": "What the guest pays, VAT included when charged: show it as the total."
                          },
                          "priceOnTheDayPence": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "Charged instead for a dated ticket bought for today; null when the ticket has one price."
                          },
                          "includesVat": {
                            "type": "boolean",
                            "description": "Say \"inc. VAT\" beside the price."
                          },
                          "dated": {
                            "type": "boolean",
                            "description": "A visit date must be chosen (send visitDate in the checkout line)."
                          },
                          "days": {
                            "type": "integer",
                            "description": "Days the ticket covers."
                          },
                          "dayMode": {
                            "type": "string",
                            "enum": [
                              "consecutive",
                              "any"
                            ],
                            "description": "\"consecutive\" (days in a row from the visit date) or \"any\" (any days within its valid dates)."
                          },
                          "daysLine": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "\"2-day ticket: the day you choose and the day after\", or null for a one-day ticket."
                          },
                          "validFrom": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "On sale for visits from and until (YYYY-MM-DD), when limited."
                          },
                          "validTo": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "reentryLine": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "\"You can leave and come back on the same day.\", when the park chose to say so."
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "description",
                          "photo",
                          "pricePence",
                          "priceOnTheDayPence",
                          "includesVat",
                          "dated",
                          "days",
                          "dayMode",
                          "daysLine",
                          "validFrom",
                          "validTo",
                          "reentryLine"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "onSaleOnline",
                    "maxPerOrder",
                    "items"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "onSaleOnline": true,
                  "maxPerOrder": 20,
                  "items": [
                    {
                      "id": "cmt50rksr001uuvf8hycjp0qj",
                      "name": "Dated Entry Ticket",
                      "description": null,
                      "photo": null,
                      "pricePence": 2900,
                      "priceOnTheDayPence": null,
                      "includesVat": false,
                      "dated": true,
                      "days": 1,
                      "dayMode": "consecutive",
                      "daysLine": null,
                      "validFrom": null,
                      "validTo": null,
                      "reentryLine": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tickets/availability": {
      "get": {
        "operationId": "availability",
        "summary": "Ticket availability",
        "description": "A month of days: which can be booked for each dated ticket, which are sold out, closed or too late, and when few places are left.",
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": false,
            "description": "YYYY-MM; this month when left out. Past months are refused.",
            "schema": {
              "type": "string"
            },
            "example": "2026-11"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "public, max-age=60, stale-while-revalidate=300"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string"
                    },
                    "todayCutOff": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Today's last time to buy for today (HH:mm), when the park has one."
                    },
                    "days": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "date": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "available",
                              "sold_out",
                              "closed",
                              "too_late"
                            ],
                            "description": "\"available\", \"sold_out\", \"closed\" or \"too_late\" (today, after the cut-off)."
                          },
                          "label": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "tickets": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string"
                                },
                                "available": {
                                  "type": "boolean"
                                },
                                "fewLeft": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "Places left when 10 or fewer, else null."
                                }
                              },
                              "required": [
                                "id",
                                "available",
                                "fewLeft"
                              ],
                              "additionalProperties": false
                            },
                            "description": "Each dated ticket on this day. Undated tickets need no day."
                          }
                        },
                        "required": [
                          "date",
                          "status",
                          "label",
                          "tickets"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "from",
                    "to",
                    "todayCutOff",
                    "days"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "from": "2026-10-29",
                  "to": "2026-10-31",
                  "todayCutOff": "19:30",
                  "days": [
                    {
                      "date": "2026-10-29",
                      "status": "too_late",
                      "label": "Halloween late opening",
                      "tickets": [
                        {
                          "id": "cmt50rksr001uuvf8hycjp0qj",
                          "available": false,
                          "fewLeft": null
                        }
                      ]
                    },
                    {
                      "date": "2026-10-30",
                      "status": "available",
                      "label": "Halloween late opening",
                      "tickets": [
                        {
                          "id": "cmt50rksr001uuvf8hycjp0qj",
                          "available": true,
                          "fewLeft": null
                        }
                      ]
                    },
                    {
                      "date": "2026-10-31",
                      "status": "available",
                      "label": "Halloween late opening",
                      "tickets": [
                        {
                          "id": "cmt50rksr001uuvf8hycjp0qj",
                          "available": true,
                          "fewLeft": null
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "BAD_REQUEST: Something in the request is wrong; the message says what, in words a guest can read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkout": {
      "post": {
        "operationId": "checkout",
        "summary": "Start a checkout",
        "description": "Checks the order exactly as Parkstack's own ticket page does, holds the places and answers with Stripe's payment page. Up to 20 tickets an order.\n\nSend the guest to `url`. Card details are only ever typed on Stripe's page: never collect them yourself.\n\nShow the refusal's `message` to the guest as it is: it is written for them.\n\nBefore the button, link the Ticket terms and say who the guest is buying from (`seller` on `/park`).\n\nKeep `orderNumber` and `linkToken`: with them GET /orders/{orderNumber} shows the tickets, with no return address needed. A phone app leaves out `returnUrl` and `cancelUrl` and reads the order when the guest closes Stripe's page.",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The guest's email address: their tickets are sent there."
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80,
                    "description": "Required when the park asks for it at checkout; the refusal says so."
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "postcode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 12
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "ticketId": {
                          "type": "string",
                          "minLength": 1,
                          "description": "A ticket's id from GET /tickets."
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 20
                        },
                        "visitDate": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                          "description": "YYYY-MM-DD, for a dated ticket; leave out for an undated one."
                        }
                      },
                      "required": [
                        "ticketId",
                        "quantity"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 20
                  },
                  "discountCode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40,
                    "description": "A discount code the guest typed; refused with a plain message when it does not apply."
                  },
                  "voucherCode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40,
                    "description": "A gift voucher code; its balance pays first and Stripe takes the rest."
                  },
                  "returnUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri",
                    "maxLength": 500,
                    "description": "Where the guest lands after paying, on one of the key's own website addresses; it gains ?order=…&t=… for GET /orders/{orderNumber}. Leave out to use Parkstack's confirmation page."
                  },
                  "cancelUrl": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri",
                    "maxLength": 500,
                    "description": "Where \"Back\" on the payment page goes; one of the key's own addresses. Leave out for Parkstack's ticket page."
                  }
                },
                "required": [
                  "email",
                  "lines"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "description": "Send the guest here: Stripe's payment page, or the confirmation page when nothing is to pay."
                    },
                    "demo": {
                      "type": "boolean",
                      "description": "A demonstration park: no payment, and the tickets are not valid."
                    },
                    "orderNumber": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The order this checkout made. With linkToken, read it from GET /orders/{orderNumber}?t=… until its status is PAID. Null on a demonstration park, which makes no order."
                    },
                    "linkToken": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The order's private link code, for GET /orders/{orderNumber}?t=…. Keep it with the order on the guest's device only: it opens their tickets."
                    }
                  },
                  "required": [
                    "url",
                    "demo",
                    "orderNumber",
                    "linkToken"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "url": "https://checkout.stripe.com/c/pay/cs_live_a1B2c3…",
                  "demo": false,
                  "orderNumber": "RIV-2026-000123",
                  "linkToken": "q8Zt3VnK0wXyR5mJ2hLc7PbD"
                }
              }
            }
          },
          "400": {
            "description": "BAD_REQUEST: Something in the request is wrong; the message says what, in words a guest can read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "SALES_CLOSED: Tickets are not on sale online at the moment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/{orderNumber}": {
      "get": {
        "operationId": "order",
        "summary": "An order",
        "description": "An order after checkout, with a QR code per ticket once paid. Needs the link token the guest returned with.\n\nWhile `status` is PENDING the payment is still being confirmed: ask again every few seconds. Never offer to pay again.",
        "parameters": [
          {
            "name": "orderNumber",
            "in": "path",
            "required": true,
            "description": "`orderNumber` from POST /checkout, or ?order= on the address the guest returned to.",
            "schema": {
              "type": "string"
            },
            "example": "RIV-2026-000123"
          },
          {
            "name": "t",
            "in": "query",
            "required": true,
            "description": "`linkToken` from POST /checkout, or &t= on the address the guest returned to.",
            "schema": {
              "type": "string"
            },
            "example": "…"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "example": "private, no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "orderNumber": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "PENDING",
                        "PAID",
                        "PARTIALLY_REFUNDED",
                        "REFUNDED",
                        "CANCELLED",
                        "FAILED"
                      ],
                      "description": "PENDING: payment not confirmed yet, ask again. PAID or PARTIALLY_REFUNDED: show the tickets. CANCELLED or FAILED: not paid (the guest left the payment page, or it expired)."
                    },
                    "totalPence": {
                      "type": "integer",
                      "description": "What the guest paid, in pence."
                    },
                    "currency": {
                      "type": "string"
                    },
                    "lines": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string",
                            "description": "The ticket's name."
                          },
                          "quantity": {
                            "type": "integer"
                          },
                          "unitPence": {
                            "type": "integer",
                            "description": "The price paid for each, after any discount code."
                          },
                          "visitDate": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "YYYY-MM-DD for a dated ticket."
                          },
                          "lastVisitDate": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The last day a multi-day ticket covers."
                          }
                        },
                        "required": [
                          "name",
                          "quantity",
                          "unitPence",
                          "visitDate",
                          "lastVisitDate"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "tickets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "ticketNumber": {
                            "type": "string",
                            "description": "Printed under the QR code; staff can type it at the gate."
                          },
                          "name": {
                            "type": "string",
                            "description": "The ticket's name."
                          },
                          "visitDate": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "refunded": {
                            "type": "boolean",
                            "description": "A refunded ticket cannot be used: show it as refunded, without a QR code."
                          },
                          "qrPayload": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Draw this text as a QR code, exactly as given."
                          }
                        },
                        "required": [
                          "ticketNumber",
                          "name",
                          "visitDate",
                          "refunded",
                          "qrPayload"
                        ],
                        "additionalProperties": false
                      },
                      "description": "One per guest once paid. Draw qrPayload as a QR code; null once refunded."
                    },
                    "confirmationUrl": {
                      "type": "string",
                      "description": "Parkstack's confirmation page for this order, with the QR codes."
                    }
                  },
                  "required": [
                    "orderNumber",
                    "status",
                    "totalPence",
                    "currency",
                    "lines",
                    "tickets",
                    "confirmationUrl"
                  ],
                  "additionalProperties": false
                },
                "example": {
                  "orderNumber": "RIV-2026-000123",
                  "status": "PAID",
                  "totalPence": 5800,
                  "currency": "GBP",
                  "lines": [
                    {
                      "name": "Dated Entry Ticket",
                      "quantity": 2,
                      "unitPence": 2900,
                      "visitDate": "2026-10-30",
                      "lastVisitDate": "2026-10-30"
                    }
                  ],
                  "tickets": [
                    {
                      "ticketNumber": "RIV-2026-000123-1",
                      "name": "Dated Entry Ticket",
                      "visitDate": "2026-10-30",
                      "refunded": false,
                      "qrPayload": "PARKSTACK:TICKET:RIV-2026-000123-1:K7QF2M9XWD4H"
                    },
                    {
                      "ticketNumber": "RIV-2026-000123-2",
                      "name": "Dated Entry Ticket",
                      "visitDate": "2026-10-30",
                      "refunded": false,
                      "qrPayload": "PARKSTACK:TICKET:RIV-2026-000123-2:P3RN8VTC6JYB"
                    }
                  ],
                  "confirmationUrl": "https://riverside.parkstack.net/tickets/success?order=RIV-2026-000123&t=…"
                }
              }
            }
          },
          "401": {
            "description": "KEY_MISSING: No key was sent. Send it as Authorization: Bearer pk_live_… KEY_INVALID: The key is not one Parkstack knows, or the park's owner has turned it off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "ORIGIN_NOT_ALLOWED: A browser called from a website address the key does not list. Call from your server, or ask the park's owner to add the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: Nothing published at that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: Too many requests this minute. Wait for Retry-After seconds, and cache what you read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "SITE_OFFLINE: The park's website is offline (its owner took it offline, or its plan ended). Show a short holding message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "siteKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A site key, pk_live_…. The public demo key pk_live_demoRiversideParkstackSiteApiKey reads Riverside, a demonstration park whose checkout takes no payment."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "KEY_MISSING",
              "KEY_INVALID",
              "ORIGIN_NOT_ALLOWED",
              "RATE_LIMITED",
              "SITE_OFFLINE",
              "NOT_FOUND",
              "BAD_REQUEST",
              "SALES_CLOSED"
            ]
          },
          "message": {
            "type": "string",
            "description": "Written for people: show it to the guest when it is about their order."
          },
          "docs": {
            "type": "string",
            "description": "How to use the API: the instructions file, written for AI coding tools and developers."
          }
        },
        "required": [
          "code",
          "message",
          "docs"
        ],
        "additionalProperties": false
      }
    }
  }
}
