{
    "openapi": "3.1.0",
    "info": {
        "title": "Village Hall Hub public API",
        "version": "1.0.0",
        "description": "Run your hall's own website or software on top of its Village Hall Hub diary:\nshow free times, take booking requests, list what's on, and keep hirers,\ninvoices and payments in step.\n\n## Getting started\n\n1. In Village Hall Hub, go to **Settings, then API Keys**, and press **Make a\n   key**. Tick only what it needs.\n2. Send it on every request as `Authorization: Bearer <key>`.\n3. Try `GET \/bookings`.\n\nA key belongs to one hall and can never do more than the person who made it.\nKeep it on your server, never in a web page.\n\n## Permissions\n\nEach endpoint needs one of the permissions ticked on the key, such as\n`bookings.read`. A key without it gets a `403` naming what is missing.\n\n## Making changes\n\nEvery request that changes something needs an `Idempotency-Key` header: any\nunique string, such as a UUID. Send the same key again and you get the first\nanswer back without anything happening twice.\n\n## Times, dates and money\n\n- **Times** are the hall's local time (Europe\/London) in ISO 8601 with the\n  offset, such as `2026-07-04T19:00:00+01:00`. Sent without an offset, they\n  are read as hall time.\n- **Dates** are `YYYY-MM-DD`. Fields ending `_on` are dates; fields ending\n  `_at` are times.\n- **Money** is pounds as a string with two decimals, such as `\"12.50\"`.\n\n## Lists and errors\n\nLists come a page at a time, 25 to a page (`per_page` up to 100), with\n`links` and `meta` for the next page.\n\nErrors are JSON with a `message`. Validation errors (`422`) add `errors` by\nfield; a clash with another booking (`409`) adds `conflict`.\n\n## Privacy\n\nA private booking's title always reads \"Private booking\", though its time\nstill shows as taken. Who booked, and their notes, appear only to a key with\n`hirers.read`. The committee's internal notes never appear.\n\n## Limits\n\nRequests a minute depend on the hall's plan: 60 on Community, 120 on Standard,\n600 on Professional. Going over gets a `429` with a `Retry-After` header.\n\n## Keeping a copy in step\n\nDon't fetch every booking every few minutes. Fetch everything once, then\neither:\n\n- **use a webhook** (below), and we tell you the moment something changes; or\n- **poll with `updated_since`**, sending the time your last sync started, to\n  get only what was added or changed since, cancellations included.\n\nA booking deleted outright is not in an `updated_since` answer (a webhook does\ntell you), so a full fetch once a night is a sensible safety net.\n\n## Webhooks\n\nAdd one in Village Hall Hub under **Settings, then Webhooks**. We send a\n`POST` to your address the moment something you ticked happens. Each event,\nwith its exact body, is listed under **Webhooks** in the menu.\n\n```json\n{\n  \"id\": \"8f0c2a1e-5d7b-4c1e-9a3f-2b6d1e0c4f7a\",\n  \"event\": \"booking.created\",\n  \"hall_id\": 32,\n  \"occurred_at\": \"2026-10-03T13:05:00+00:00\",\n  \"data\": { \"id\": 9613, \"title\": \"Ceilidh\", \"status\": \"provisional\" }\n}\n```\n\n`data` is exactly what this API returns for that booking, invoice or payment.\nA booking names its hirer only if the hall ticked \"Include who made the\nbooking\". **Send a test** sends a `ping`.\n\n### Checking the signature\n\nEvery message carries `X-VHH-Timestamp` and `X-VHH-Signature`. The signature\nis `v1=` followed by the HMAC-SHA256, in hex, of the timestamp, a full stop\nand the raw body, keyed with the webhook's signing secret (Settings, Webhooks,\nSigning secret). Work it out yourself and compare, and turn away a timestamp\nmore than five minutes old.\n\n```php\n$body = file_get_contents('php:\/\/input');\n$timestamp = $_SERVER['HTTP_X_VHH_TIMESTAMP'];\n$expected = 'v1='.hash_hmac('sha256', $timestamp.'.'.$body, $secret);\n\nif (! hash_equals($expected, $_SERVER['HTTP_X_VHH_SIGNATURE']) || abs(time() - (int) $timestamp) > 300) {\n    http_response_code(401);\n    exit;\n}\n```\n\n```js\nconst crypto = require('crypto');\nconst expected = 'v1=' + crypto.createHmac('sha256', secret)\n  .update(req.headers['x-vhh-timestamp'] + '.' + rawBody).digest('hex');\nconst ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-vhh-signature']));\n```\n\n### Answering, and retries\n\nAnswer with any `2xx` within 10 seconds, and do slow work afterwards. Anything\nelse, or no answer, is tried again after 1, 5 and 15 minutes, 1 hour and 3\nhours. A repeat has the same `id`, so ignore one you have already handled.\nMessages can arrive out of order: use `updated_at` in `data` to tell which is\nnewest. After 15 messages in a row fail every try, the webhook is turned off\nand the hall is shown why.\n\n### Webhook limits\n\n| Plan | Webhooks | Messages a day |\n|---|---|---|\n| Community | 1 | 500 |\n| Standard | 3 | 2,000 |\n| Professional | 10 | 10,000 |\n\nChanges past the day's allowance are not sent, and the hall sees how many, so\non an unusually busy day fetch everything with the API as well."
    },
    "servers": [
        {
            "url": "https:\/\/villagehallhub.co.uk\/api\/public\/v1"
        }
    ],
    "security": [
        {
            "http": []
        }
    ],
    "paths": {
        "\/availability": {
            "get": {
                "operationId": "public-api.availability.index",
                "description": "The busy times and the free gaps between them for one room, over up to\n92 days. Busy times are widened by the gap the hall keeps between hires,\nand days the hall is shut count as busy, so a free gap is one that can\nreally be booked.",
                "summary": "When a room is free",
                "tags": [
                    "Bookings"
                ],
                "parameters": [
                    {
                        "name": "room_id",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "from",
                        "in": "query",
                        "required": true,
                        "description": "First day (YYYY-MM-DD).",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "required": true,
                        "description": "Last day (YYYY-MM-DD), no more than 92 days after the first.",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "object",
                                            "properties": {
                                                "room": {
                                                    "type": "object",
                                                    "properties": {
                                                        "id": {
                                                            "type": "string"
                                                        },
                                                        "name": {
                                                            "type": "string"
                                                        }
                                                    },
                                                    "required": [
                                                        "id",
                                                        "name"
                                                    ]
                                                },
                                                "from": {
                                                    "type": "string"
                                                },
                                                "to": {
                                                    "type": "string"
                                                },
                                                "buffer_minutes": {
                                                    "type": "integer"
                                                },
                                                "busy": {
                                                    "type": "array",
                                                    "items": {}
                                                },
                                                "free": {
                                                    "type": "array",
                                                    "items": {}
                                                }
                                            },
                                            "required": [
                                                "room",
                                                "from",
                                                "to",
                                                "buffer_minutes",
                                                "busy",
                                                "free"
                                            ]
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/bookings": {
            "get": {
                "operationId": "public-api.bookings.index",
                "description": "The hall's diary, earliest first. Cancelled bookings are left out\nunless you ask for them.\n\nTo keep your own copy up to date, fetch everything once, then send\nupdated_since with the time your previous sync started: you get only\nwhat was added or changed since, cancellations included, which is\nusually nothing at all. A booking the hall deletes outright does not\nappear, so run a full fetch now and then, once a night is plenty.",
                "summary": "List bookings",
                "tags": [
                    "Bookings"
                ],
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "from",
                        "in": "query",
                        "description": "Bookings on or after this date (YYYY-MM-DD).",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "description": "Bookings on or before this date (YYYY-MM-DD).",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "room_id",
                        "in": "query",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "hirer_id",
                        "in": "query",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "schema": {
                            "$ref": "#\/components\/schemas\/BookingStatus"
                        }
                    },
                    {
                        "name": "include_cancelled",
                        "in": "query",
                        "description": "Cancelled bookings are left out unless this is true.",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "updated_since",
                        "in": "query",
                        "description": "Only bookings added or changed at or after this moment (ISO 8601,\ne.g. 2026-10-03T10:30:00+01:00). For keeping a copy in step:\nsend the time your last sync started, and cancelled bookings\nare included so you see those too.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Array of `BookingResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/BookingResource"
                                            }
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            },
            "post": {
                "operationId": "public-api.bookings.store",
                "description": "A booking request lands as provisional unless you say otherwise, so\nthe hall still says yes, and the hall's staff are told it is waiting\nunless you send tell_hall as false. The person booking is not emailed\nuntil the hall confirms. If the room is taken - including the gap the\nhall keeps between hires - the answer is 409 with what it clashes with.",
                "summary": "Make a booking",
                "tags": [
                    "Bookings"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/StoreBookingRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "`BookingResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/BookingResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/bookings\/{booking}": {
            "get": {
                "operationId": "public-api.bookings.show",
                "summary": "Get a booking",
                "tags": [
                    "Bookings"
                ],
                "parameters": [
                    {
                        "name": "booking",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "`BookingResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/BookingResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            },
            "patch": {
                "operationId": "public-api.bookings.update",
                "description": "Send only what changes. Confirming an enquiry or a provisional booking\napplies the hall's usual deposit and emails the hirer, exactly as\npressing Approve on screen does - unless the hall has turned that email\noff in its Booking Settings. A new time is checked for\nclashes like a new booking.",
                "summary": "Change a booking",
                "tags": [
                    "Bookings"
                ],
                "parameters": [
                    {
                        "name": "booking",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/UpdateBookingRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "`BookingResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/BookingResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/bookings\/{booking}\/decline": {
            "post": {
                "operationId": "public-api.bookings.decline",
                "description": "Says no to an enquiry or a provisional booking, which is cancelled. The\nhirer is emailed only if you ask, with your reason if you give one.",
                "summary": "Decline a booking request",
                "tags": [
                    "Bookings"
                ],
                "parameters": [
                    {
                        "name": "booking",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/DeclineBookingRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "`BookingResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/BookingResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "const": "Only an enquiry or a provisional booking can be declined. Cancel a confirmed booking by setting its status to cancelled."
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/closures": {
            "get": {
                "operationId": "public-api.closures.index",
                "description": "Days the hall is shut, soonest first. Closures already over are left\nout.",
                "summary": "List closures",
                "tags": [
                    "Closures"
                ],
                "responses": {
                    "200": {
                        "description": "Array of `ClosureResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/ClosureResource"
                                            }
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            },
            "post": {
                "operationId": "public-api.closures.store",
                "description": "Stops anything new being booked on those days. Bookings already made\nare not cancelled - the people who made them still need to be told.",
                "summary": "Close the hall",
                "tags": [
                    "Closures"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/StoreClosureRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "`ClosureResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/ClosureResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/closures\/{closure}": {
            "delete": {
                "operationId": "public-api.closures.destroy",
                "description": "The hall is open on those days again.",
                "summary": "Remove a closure",
                "tags": [
                    "Closures"
                ],
                "parameters": [
                    {
                        "name": "closure",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "204": {
                        "description": "No content"
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/venue": {
            "get": {
                "operationId": "public-api.venue.show",
                "description": "The hall this key belongs to: its contact details, the time zone and\ncurrency everything in this API uses, and the gap it keeps between\nhires.",
                "summary": "Get the hall",
                "tags": [
                    "Hall"
                ],
                "responses": {
                    "200": {
                        "description": "`VenueResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/VenueResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/hirers": {
            "get": {
                "operationId": "public-api.hirers.index",
                "description": "The people and groups who hire the hall, by name.",
                "summary": "List hirers",
                "tags": [
                    "Hirers"
                ],
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "search",
                        "in": "query",
                        "description": "Part of a name, organisation or email address.",
                        "schema": {
                            "type": "string",
                            "maxLength": 100
                        }
                    },
                    {
                        "name": "include_inactive",
                        "in": "query",
                        "description": "Hirers the hall has marked inactive are left out unless this is true.",
                        "schema": {
                            "type": "boolean"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Paginated set of `HirerResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/HirerResource"
                                            }
                                        },
                                        "links": {
                                            "type": "object",
                                            "properties": {
                                                "first": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "last": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "prev": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "next": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                }
                                            },
                                            "required": [
                                                "first",
                                                "last",
                                                "prev",
                                                "next"
                                            ]
                                        },
                                        "meta": {
                                            "type": "object",
                                            "properties": {
                                                "current_page": {
                                                    "type": "integer",
                                                    "minimum": 1
                                                },
                                                "from": {
                                                    "type": [
                                                        "integer",
                                                        "null"
                                                    ],
                                                    "minimum": 1
                                                },
                                                "last_page": {
                                                    "type": "integer",
                                                    "minimum": 1
                                                },
                                                "links": {
                                                    "type": "array",
                                                    "description": "Generated paginator links.",
                                                    "items": {
                                                        "type": "object",
                                                        "properties": {
                                                            "url": {
                                                                "type": [
                                                                    "string",
                                                                    "null"
                                                                ]
                                                            },
                                                            "label": {
                                                                "type": "string"
                                                            },
                                                            "active": {
                                                                "type": "boolean"
                                                            }
                                                        },
                                                        "required": [
                                                            "url",
                                                            "label",
                                                            "active"
                                                        ]
                                                    }
                                                },
                                                "path": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ],
                                                    "description": "Base path for paginator generated URLs."
                                                },
                                                "per_page": {
                                                    "type": "integer",
                                                    "description": "Number of items shown per page.",
                                                    "minimum": 0
                                                },
                                                "to": {
                                                    "type": [
                                                        "integer",
                                                        "null"
                                                    ],
                                                    "description": "Number of the last item in the slice.",
                                                    "minimum": 1
                                                },
                                                "total": {
                                                    "type": "integer",
                                                    "description": "Total number of items being paginated.",
                                                    "minimum": 0
                                                }
                                            },
                                            "required": [
                                                "current_page",
                                                "from",
                                                "last_page",
                                                "links",
                                                "path",
                                                "per_page",
                                                "to",
                                                "total"
                                            ]
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "links",
                                        "meta"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            },
            "post": {
                "operationId": "public-api.hirers.store",
                "description": "Look for the hirer first with ?search=, so the same person is not added\ntwice.",
                "summary": "Add a hirer",
                "tags": [
                    "Hirers"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/StoreHirerRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "`HirerResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "allOf": [
                                                {
                                                    "$ref": "#\/components\/schemas\/HirerResource"
                                                },
                                                {
                                                    "type": "object",
                                                    "required": [
                                                        "hirer_type"
                                                    ]
                                                }
                                            ]
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/hirers\/{hirer}": {
            "get": {
                "operationId": "public-api.hirers.show",
                "summary": "Get a hirer",
                "tags": [
                    "Hirers"
                ],
                "parameters": [
                    {
                        "name": "hirer",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "`HirerResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/HirerResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            },
            "patch": {
                "operationId": "public-api.hirers.update",
                "description": "Send only what changes.",
                "summary": "Change a hirer",
                "tags": [
                    "Hirers"
                ],
                "parameters": [
                    {
                        "name": "hirer",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/StoreHirerRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "`HirerResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/HirerResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/invoices": {
            "get": {
                "operationId": "public-api.invoices.index",
                "description": "Newest first. Lines and payments come with a single invoice.",
                "summary": "List invoices",
                "tags": [
                    "Invoices"
                ],
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "description": "One status, or \"unpaid\" for anything sent and not yet fully paid.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "draft",
                                "sent",
                                "part_paid",
                                "paid",
                                "overdue",
                                "void",
                                "unpaid"
                            ]
                        }
                    },
                    {
                        "name": "hirer_id",
                        "in": "query",
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Paginated set of `InvoiceResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/InvoiceResource"
                                            }
                                        },
                                        "links": {
                                            "type": "object",
                                            "properties": {
                                                "first": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "last": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "prev": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "next": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                }
                                            },
                                            "required": [
                                                "first",
                                                "last",
                                                "prev",
                                                "next"
                                            ]
                                        },
                                        "meta": {
                                            "type": "object",
                                            "properties": {
                                                "current_page": {
                                                    "type": "integer",
                                                    "minimum": 1
                                                },
                                                "from": {
                                                    "type": [
                                                        "integer",
                                                        "null"
                                                    ],
                                                    "minimum": 1
                                                },
                                                "last_page": {
                                                    "type": "integer",
                                                    "minimum": 1
                                                },
                                                "links": {
                                                    "type": "array",
                                                    "description": "Generated paginator links.",
                                                    "items": {
                                                        "type": "object",
                                                        "properties": {
                                                            "url": {
                                                                "type": [
                                                                    "string",
                                                                    "null"
                                                                ]
                                                            },
                                                            "label": {
                                                                "type": "string"
                                                            },
                                                            "active": {
                                                                "type": "boolean"
                                                            }
                                                        },
                                                        "required": [
                                                            "url",
                                                            "label",
                                                            "active"
                                                        ]
                                                    }
                                                },
                                                "path": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ],
                                                    "description": "Base path for paginator generated URLs."
                                                },
                                                "per_page": {
                                                    "type": "integer",
                                                    "description": "Number of items shown per page.",
                                                    "minimum": 0
                                                },
                                                "to": {
                                                    "type": [
                                                        "integer",
                                                        "null"
                                                    ],
                                                    "description": "Number of the last item in the slice.",
                                                    "minimum": 1
                                                },
                                                "total": {
                                                    "type": "integer",
                                                    "description": "Total number of items being paginated.",
                                                    "minimum": 0
                                                }
                                            },
                                            "required": [
                                                "current_page",
                                                "from",
                                                "last_page",
                                                "links",
                                                "path",
                                                "per_page",
                                                "to",
                                                "total"
                                            ]
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "links",
                                        "meta"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            },
            "post": {
                "operationId": "public-api.invoices.store",
                "description": "A draft invoice with your own lines. Send it with the send endpoint.",
                "summary": "Raise an invoice",
                "tags": [
                    "Invoices"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/StoreInvoiceRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "`InvoiceResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "allOf": [
                                                {
                                                    "$ref": "#\/components\/schemas\/InvoiceResource"
                                                },
                                                {
                                                    "type": "object",
                                                    "required": [
                                                        "hirer",
                                                        "lines",
                                                        "payments"
                                                    ]
                                                }
                                            ]
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/invoices\/{invoice}": {
            "get": {
                "operationId": "public-api.invoices.show",
                "description": "With its lines and the payments made against it.",
                "summary": "Get an invoice",
                "tags": [
                    "Invoices"
                ],
                "parameters": [
                    {
                        "name": "invoice",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "`InvoiceResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "allOf": [
                                                {
                                                    "$ref": "#\/components\/schemas\/InvoiceResource"
                                                },
                                                {
                                                    "type": "object",
                                                    "required": [
                                                        "hirer",
                                                        "lines",
                                                        "payments"
                                                    ]
                                                }
                                            ]
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/invoices\/{invoice}\/pdf": {
            "get": {
                "operationId": "public-api.invoices.pdf",
                "description": "The invoice as a PDF, as the hirer receives it.",
                "summary": "Download an invoice",
                "tags": [
                    "Invoices"
                ],
                "parameters": [
                    {
                        "name": "invoice",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/invoices\/{invoice}\/send": {
            "post": {
                "operationId": "public-api.invoices.send",
                "description": "Only a draft can be sent, so a second call never bills a hirer twice.\nWith email_hirer false it is only marked as sent - for an invoice\nhanded over by hand.",
                "summary": "Send an invoice",
                "tags": [
                    "Invoices"
                ],
                "parameters": [
                    {
                        "name": "invoice",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/SendInvoiceRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "`InvoiceResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "allOf": [
                                                {
                                                    "$ref": "#\/components\/schemas\/InvoiceResource"
                                                },
                                                {
                                                    "type": "object",
                                                    "required": [
                                                        "hirer",
                                                        "lines",
                                                        "payments"
                                                    ]
                                                }
                                            ]
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "const": "That invoice has already been sent. Only a draft can be sent."
                                        }
                                    },
                                    "required": [
                                        "message"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/payments": {
            "get": {
                "operationId": "public-api.payments.index",
                "description": "Money received, newest first - including card payments hirers made\nfrom their invoice.",
                "summary": "List payments",
                "tags": [
                    "Invoices"
                ],
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "invoice_id",
                        "in": "query",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "method",
                        "in": "query",
                        "schema": {
                            "$ref": "#\/components\/schemas\/PaymentMethod"
                        }
                    },
                    {
                        "name": "from",
                        "in": "query",
                        "description": "Paid on or after this date (YYYY-MM-DD).",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "description": "Paid on or before this date (YYYY-MM-DD).",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Paginated set of `PaymentResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/PaymentResource"
                                            }
                                        },
                                        "links": {
                                            "type": "object",
                                            "properties": {
                                                "first": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "last": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "prev": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "next": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                }
                                            },
                                            "required": [
                                                "first",
                                                "last",
                                                "prev",
                                                "next"
                                            ]
                                        },
                                        "meta": {
                                            "type": "object",
                                            "properties": {
                                                "current_page": {
                                                    "type": "integer",
                                                    "minimum": 1
                                                },
                                                "from": {
                                                    "type": [
                                                        "integer",
                                                        "null"
                                                    ],
                                                    "minimum": 1
                                                },
                                                "last_page": {
                                                    "type": "integer",
                                                    "minimum": 1
                                                },
                                                "links": {
                                                    "type": "array",
                                                    "description": "Generated paginator links.",
                                                    "items": {
                                                        "type": "object",
                                                        "properties": {
                                                            "url": {
                                                                "type": [
                                                                    "string",
                                                                    "null"
                                                                ]
                                                            },
                                                            "label": {
                                                                "type": "string"
                                                            },
                                                            "active": {
                                                                "type": "boolean"
                                                            }
                                                        },
                                                        "required": [
                                                            "url",
                                                            "label",
                                                            "active"
                                                        ]
                                                    }
                                                },
                                                "path": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ],
                                                    "description": "Base path for paginator generated URLs."
                                                },
                                                "per_page": {
                                                    "type": "integer",
                                                    "description": "Number of items shown per page.",
                                                    "minimum": 0
                                                },
                                                "to": {
                                                    "type": [
                                                        "integer",
                                                        "null"
                                                    ],
                                                    "description": "Number of the last item in the slice.",
                                                    "minimum": 1
                                                },
                                                "total": {
                                                    "type": "integer",
                                                    "description": "Total number of items being paginated.",
                                                    "minimum": 0
                                                }
                                            },
                                            "required": [
                                                "current_page",
                                                "from",
                                                "last_page",
                                                "links",
                                                "path",
                                                "per_page",
                                                "to",
                                                "total"
                                            ]
                                        }
                                    },
                                    "required": [
                                        "data",
                                        "links",
                                        "meta"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/invoices\/{invoice}\/payments": {
            "post": {
                "operationId": "public-api.payments.store",
                "description": "Money taken outside Village Hall Hub - cash, a bank transfer or a\ncheque - against an invoice. The invoice's paid and outstanding\namounts update straight away.",
                "summary": "Record a payment",
                "tags": [
                    "Invoices"
                ],
                "parameters": [
                    {
                        "name": "invoice",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/StorePaymentRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "`PaymentResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/PaymentResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        },
        "\/hire-types": {
            "get": {
                "operationId": "public-api.hire-types.index",
                "description": "The kinds of hire the hall sorts its bookings into, with their colours.",
                "summary": "List hire types",
                "tags": [
                    "Prices"
                ],
                "responses": {
                    "200": {
                        "description": "Array of `HireTypeResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/HireTypeResource"
                                            }
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/rate-cards": {
            "get": {
                "operationId": "public-api.rate-cards.index",
                "description": "The hall's price lists, each with what every room on it costs today\nand any price change already booked in.",
                "summary": "List rate cards",
                "tags": [
                    "Prices"
                ],
                "responses": {
                    "200": {
                        "description": "Array of `RateCardResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/RateCardResource"
                                            }
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/extras": {
            "get": {
                "operationId": "public-api.extras.index",
                "description": "What can be added to a hire, and what each costs.",
                "summary": "List extras",
                "tags": [
                    "Prices"
                ],
                "responses": {
                    "200": {
                        "description": "Array of `ExtraResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/ExtraResource"
                                            }
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/rooms": {
            "get": {
                "operationId": "public-api.rooms.index",
                "description": "Every space the hall hires out, by name. Rooms the hall has switched\noff are left out.",
                "summary": "List rooms",
                "tags": [
                    "Rooms"
                ],
                "responses": {
                    "200": {
                        "description": "Array of `RoomResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/RoomResource"
                                            }
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/rooms\/{room}": {
            "get": {
                "operationId": "public-api.rooms.show",
                "summary": "Get a room",
                "tags": [
                    "Rooms"
                ],
                "parameters": [
                    {
                        "name": "room",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "`RoomResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "$ref": "#\/components\/schemas\/RoomResource"
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    }
                }
            }
        },
        "\/events": {
            "get": {
                "operationId": "public-api.events.index",
                "description": "The bookings the hall has chosen to advertise, soonest first, from\ntoday unless you say otherwise. Private and cancelled bookings never\nappear, and neither does who booked. Built for a What's On page.",
                "summary": "List what's on",
                "tags": [
                    "What's on"
                ],
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "from",
                        "in": "query",
                        "description": "Events on or after this date (YYYY-MM-DD). Defaults to today.",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "description": "Events on or before this date (YYYY-MM-DD).",
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "room_id",
                        "in": "query",
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Array of `EventResource`",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/EventResource"
                                            }
                                        }
                                    },
                                    "required": [
                                        "data"
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/AuthenticationException"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/ValidationException"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "http": {
                "type": "http",
                "scheme": "bearer"
            }
        },
        "schemas": {
            "BookingResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "title": {
                        "type": "string"
                    },
                    "is_private": {
                        "type": "boolean"
                    },
                    "status": {
                        "type": "string",
                        "description": "One of enquiry, provisional, confirmed or cancelled."
                    },
                    "not_going_ahead_reason": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Why a cancelled booking did not go ahead, if the hall said: one\nof hirer_did_not_go_ahead, not_available, not_suitable, price\nor other."
                    },
                    "starts_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "ends_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "room": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string"
                            }
                        },
                        "required": [
                            "id",
                            "name"
                        ]
                    },
                    "hire_type": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string"
                            }
                        },
                        "required": [
                            "id",
                            "name"
                        ]
                    },
                    "hirer": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string"
                            },
                            "organisation_name": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            }
                        },
                        "required": [
                            "id",
                            "name",
                            "organisation_name"
                        ]
                    },
                    "heating": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "description": "What the room should be heated to for this booking, in degrees\nCelsius, from the heating level the hall picked for it. Null\nwhen the hall has not set one. For heating systems that follow\nthe diary.",
                        "properties": {
                            "level": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "temperature": {
                                "type": [
                                    "integer",
                                    "null"
                                ]
                            }
                        },
                        "required": [
                            "level",
                            "temperature"
                        ]
                    },
                    "attendee_count": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "is_free": {
                        "type": "boolean"
                    },
                    "is_advertised": {
                        "type": "boolean",
                        "description": "Whether the hall shows this booking on its What's On list."
                    },
                    "notes_for_hirer": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The hirer's own note on the booking."
                    },
                    "series_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The first booking of the weekly or monthly series this belongs to."
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "required": [
                    "id",
                    "title",
                    "is_private",
                    "status",
                    "not_going_ahead_reason",
                    "starts_at",
                    "ends_at",
                    "heating",
                    "attendee_count",
                    "is_free",
                    "is_advertised",
                    "series_id",
                    "created_at",
                    "updated_at"
                ],
                "title": "BookingResource"
            },
            "BookingStatus": {
                "type": "string",
                "enum": [
                    "enquiry",
                    "provisional",
                    "confirmed",
                    "cancelled"
                ],
                "title": "BookingStatus"
            },
            "ClosureResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "starts_on": {
                        "type": "string"
                    },
                    "ends_on": {
                        "type": "string"
                    }
                },
                "required": [
                    "id",
                    "name",
                    "starts_on",
                    "ends_on"
                ],
                "title": "ClosureResource"
            },
            "DeclineBookingRequest": {
                "type": "object",
                "properties": {
                    "tell_hirer": {
                        "type": "boolean",
                        "description": "Email the hirer to say no. Off unless you ask."
                    },
                    "reason": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Included in the email, if the hirer is told.",
                        "maxLength": 1000
                    },
                    "not_going_ahead_reason": {
                        "anyOf": [
                            {
                                "description": "Why it did not go ahead, for the hall's own records: one of\nhirer_did_not_go_ahead, not_available, not_suitable, price or\nother. Never sent to the hirer.",
                                "$ref": "#\/components\/schemas\/NotGoingAheadReason"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    }
                },
                "title": "DeclineBookingRequest"
            },
            "EventResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "title": {
                        "type": "string"
                    },
                    "starts_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "ends_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "room": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string"
                            }
                        },
                        "required": [
                            "id",
                            "name"
                        ]
                    },
                    "hire_type": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string"
                            }
                        },
                        "required": [
                            "id",
                            "name"
                        ]
                    },
                    "series_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    }
                },
                "required": [
                    "id",
                    "title",
                    "starts_at",
                    "ends_at",
                    "room",
                    "hire_type",
                    "series_id"
                ],
                "title": "EventResource"
            },
            "ExtraResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "unit_price": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "unit_label": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "What one unit is, such as \"per hour\" or \"per session\"."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "description",
                    "unit_price",
                    "unit_label"
                ],
                "title": "ExtraResource"
            },
            "HireTypeResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "colour": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "required": [
                    "id",
                    "name",
                    "colour"
                ],
                "title": "HireTypeResource"
            },
            "HirerResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "organisation_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "phone": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "address": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "hirer_type": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string"
                            }
                        },
                        "required": [
                            "id",
                            "name"
                        ]
                    },
                    "rate_card_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The rate card this hirer is charged on, when they have their own."
                    },
                    "is_active": {
                        "type": "boolean"
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "required": [
                    "id",
                    "name",
                    "organisation_name",
                    "email",
                    "phone",
                    "address",
                    "rate_card_id",
                    "is_active",
                    "created_at",
                    "updated_at"
                ],
                "title": "HirerResource"
            },
            "InvoiceResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "number": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string",
                        "description": "One of draft, sent, part_paid, paid, overdue or void."
                    },
                    "is_deposit": {
                        "type": "boolean"
                    },
                    "issue_date": {
                        "type": "string"
                    },
                    "due_date": {
                        "type": "string"
                    },
                    "is_overdue": {
                        "type": "boolean"
                    },
                    "currency": {
                        "type": "string",
                        "const": "GBP"
                    },
                    "subtotal": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "vat": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "total": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "paid": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "outstanding": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "hirer": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string"
                            },
                            "organisation_name": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            }
                        },
                        "required": [
                            "id",
                            "name",
                            "organisation_name"
                        ]
                    },
                    "lines": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "description": {
                                    "type": "string"
                                },
                                "quantity": {
                                    "type": "number"
                                },
                                "unit_price": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "vat_rate": {
                                    "type": "number"
                                },
                                "total": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "booking_id": {
                                    "type": [
                                        "integer",
                                        "null"
                                    ]
                                }
                            },
                            "required": [
                                "description",
                                "quantity",
                                "unit_price",
                                "vat_rate",
                                "total",
                                "booking_id"
                            ]
                        }
                    },
                    "payments": {
                        "type": "array",
                        "items": {
                            "$ref": "#\/components\/schemas\/PaymentResource"
                        }
                    },
                    "view_url": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The page the hirer views the invoice on, and pays it by card\nwhere the hall takes card payments."
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "updated_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "required": [
                    "id",
                    "number",
                    "status",
                    "is_deposit",
                    "issue_date",
                    "due_date",
                    "is_overdue",
                    "currency",
                    "subtotal",
                    "vat",
                    "total",
                    "paid",
                    "outstanding",
                    "view_url",
                    "created_at",
                    "updated_at"
                ],
                "title": "InvoiceResource"
            },
            "NotGoingAheadReason": {
                "type": "string",
                "description": "Why an enquiry or a booking did not go ahead. Kept beside the Cancelled status rather than as statuses of its own: the calendar, the reports, the phone app and the API all know four statuses, and a hall that never fills this in sees nothing change. Asked for by Binfield Memorial Hall, to see which enquiries never became bookings.\n",
                "enum": [
                    "hirer_did_not_go_ahead",
                    "not_available",
                    "not_suitable",
                    "price",
                    "other"
                ],
                "title": "NotGoingAheadReason"
            },
            "PaymentMethod": {
                "type": "string",
                "enum": [
                    "cash",
                    "bacs",
                    "cheque",
                    "stripe",
                    "gocardless"
                ],
                "title": "PaymentMethod"
            },
            "PaymentResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "invoice_id": {
                        "type": "integer"
                    },
                    "amount": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "method": {
                        "type": "string",
                        "description": "How it was paid: cash, bacs, cheque, stripe (by card) and so on."
                    },
                    "reference": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "paid_on": {
                        "type": "string"
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "required": [
                    "id",
                    "invoice_id",
                    "amount",
                    "method",
                    "reference",
                    "paid_on",
                    "created_at"
                ],
                "title": "PaymentResource"
            },
            "RateCardResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "type": {
                        "type": "string"
                    },
                    "is_default": {
                        "type": "boolean",
                        "description": "Used for hirers who have no rate card of their own."
                    },
                    "rooms": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "room_id": {
                                    "type": "integer"
                                },
                                "room_name": {
                                    "type": "string"
                                },
                                "prices": {
                                    "type": "object",
                                    "properties": {
                                        "hourly": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "first_hour": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "fixed": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        }
                                    },
                                    "required": [
                                        "hourly",
                                        "first_hour",
                                        "fixed"
                                    ]
                                },
                                "upcoming": {
                                    "type": [
                                        "object",
                                        "null"
                                    ],
                                    "description": "The next price change, if the hall has booked one in.",
                                    "properties": {
                                        "starts_on": {
                                            "type": "string"
                                        },
                                        "prices": {
                                            "type": "object",
                                            "properties": {
                                                "hourly": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "first_hour": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "fixed": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                }
                                            },
                                            "required": [
                                                "hourly",
                                                "first_hour",
                                                "fixed"
                                            ]
                                        }
                                    },
                                    "required": [
                                        "starts_on",
                                        "prices"
                                    ]
                                }
                            },
                            "required": [
                                "room_id",
                                "room_name",
                                "prices",
                                "upcoming"
                            ]
                        }
                    }
                },
                "required": [
                    "id",
                    "name",
                    "type",
                    "is_default",
                    "rooms"
                ],
                "title": "RateCardResource"
            },
            "RoomResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "capacity": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "colour": {
                        "type": "string",
                        "description": "Hex colour the hall uses for this room, such as \"#0284c7\"."
                    },
                    "is_active": {
                        "type": "boolean"
                    },
                    "prices": {
                        "type": "object",
                        "description": "The room's own prices, charged when a booking has no rate card.\nA room is priced either by the hour or at a fixed price for the\nwhole hire, never both.",
                        "properties": {
                            "hourly": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "first_hour": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "fixed": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            }
                        },
                        "required": [
                            "hourly",
                            "first_hour",
                            "fixed"
                        ]
                    }
                },
                "required": [
                    "id",
                    "name",
                    "description",
                    "capacity",
                    "colour",
                    "is_active",
                    "prices"
                ],
                "title": "RoomResource"
            },
            "SendInvoiceRequest": {
                "type": "object",
                "properties": {
                    "email_hirer": {
                        "type": "boolean",
                        "description": "True emails the invoice to the hirer; false only marks it as sent."
                    }
                },
                "required": [
                    "email_hirer"
                ],
                "title": "SendInvoiceRequest"
            },
            "StoreBookingRequest": {
                "type": "object",
                "description": "A booking request from a hall's own website or software.\n\nTimes are the hall's local time, ISO 8601: \"2026-10-03T10:00\" or with an\noffset, \"2026-10-03T10:00:00+01:00\".",
                "properties": {
                    "room_id": {
                        "type": "integer"
                    },
                    "hirer_id": {
                        "type": "integer"
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255
                    },
                    "starts_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "ends_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "status": {
                        "type": "string",
                        "description": "Defaults to provisional, so the hall still says yes.",
                        "enum": [
                            "enquiry",
                            "provisional",
                            "confirmed"
                        ]
                    },
                    "hire_type_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "attendee_count": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "minimum": 0,
                        "maximum": 100000
                    },
                    "is_private": {
                        "type": "boolean"
                    },
                    "notes_for_hirer": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The hirer's own note, seen by the hall and the hirer.",
                        "maxLength": 5000
                    },
                    "internal_notes": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "A note for the committee only. Never returned by this API.",
                        "maxLength": 5000
                    },
                    "tell_hall": {
                        "type": "boolean",
                        "description": "Whether the hall's staff are emailed, and told in the phone\napp, that a request is waiting. Defaults to true for an enquiry\nor a provisional booking; send false when loading many at once.\nA booking made as confirmed never alerts anybody."
                    }
                },
                "required": [
                    "room_id",
                    "hirer_id",
                    "title",
                    "starts_at",
                    "ends_at"
                ],
                "title": "StoreBookingRequest"
            },
            "StoreClosureRequest": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "What it is, such as \"Christmas\" or \"Floor being sanded\".",
                        "maxLength": 255
                    },
                    "starts_on": {
                        "type": "string",
                        "format": "date"
                    },
                    "ends_on": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date",
                        "description": "The last day shut. Leave out for a single day."
                    }
                },
                "required": [
                    "name",
                    "starts_on"
                ],
                "title": "StoreClosureRequest"
            },
            "StoreHirerRequest": {
                "type": "object",
                "description": "A new hirer, or (sent with PATCH) a change to one - send only what changes.",
                "properties": {
                    "name": {
                        "type": "string",
                        "maxLength": 255
                    },
                    "organisation_name": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "email",
                        "maxLength": 255
                    },
                    "phone": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 50
                    },
                    "address": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 500
                    },
                    "hirer_type_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "rate_card_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The rate card to charge this hirer on."
                    },
                    "is_active": {
                        "type": "boolean"
                    }
                },
                "required": [
                    "name"
                ],
                "title": "StoreHirerRequest"
            },
            "StoreInvoiceRequest": {
                "type": "object",
                "description": "An invoice for anything, with its own lines. Invoices for bookings are\nusually raised by the hall from the diary instead.",
                "properties": {
                    "hirer_id": {
                        "type": "integer"
                    },
                    "due_date": {
                        "type": "string",
                        "format": "date"
                    },
                    "notes": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Printed on the invoice.",
                        "maxLength": 2000
                    },
                    "covering_note": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The message in the email the invoice is sent with.",
                        "maxLength": 2000
                    },
                    "lines": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "description": {
                                    "type": "string",
                                    "maxLength": 255
                                },
                                "quantity": {
                                    "type": "number",
                                    "minimum": 0.01,
                                    "maximum": 9999
                                },
                                "unit_price": {
                                    "type": "number",
                                    "minimum": 0,
                                    "maximum": 999999
                                },
                                "vat_rate": {
                                    "$ref": "#\/components\/schemas\/VatRate"
                                },
                                "booking_id": {
                                    "type": [
                                        "integer",
                                        "null"
                                    ]
                                }
                            },
                            "required": [
                                "description",
                                "quantity",
                                "unit_price"
                            ]
                        },
                        "minItems": 1,
                        "maxItems": 50
                    }
                },
                "required": [
                    "hirer_id",
                    "lines"
                ],
                "title": "StoreInvoiceRequest"
            },
            "StorePaymentRequest": {
                "type": "object",
                "description": "A payment taken outside Village Hall Hub - cash, a bank transfer, a cheque.\nCard payments made through the hall's invoice page are recorded on their own.",
                "properties": {
                    "amount": {
                        "type": "number",
                        "maximum": 1000000
                    },
                    "method": {
                        "type": "string",
                        "enum": [
                            "cash",
                            "bacs",
                            "cheque"
                        ]
                    },
                    "reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255
                    },
                    "paid_on": {
                        "type": "string",
                        "format": "date",
                        "description": "The day the money arrived (YYYY-MM-DD), within the last year."
                    }
                },
                "required": [
                    "amount",
                    "method",
                    "paid_on"
                ],
                "title": "StorePaymentRequest"
            },
            "UpdateBookingRequest": {
                "type": "object",
                "description": "A change to a booking. Send only what changes; a new time needs both\nstarts_at and ends_at.",
                "properties": {
                    "title": {
                        "type": "string",
                        "maxLength": 255
                    },
                    "starts_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "ends_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "status": {
                        "type": "string",
                        "description": "Confirming a booking applies any deposit and tells the hirer,\nexactly as confirming it on screen does.",
                        "enum": [
                            "enquiry",
                            "provisional",
                            "confirmed",
                            "cancelled"
                        ]
                    },
                    "not_going_ahead_reason": {
                        "anyOf": [
                            {
                                "description": "Why a cancelled booking did not go ahead. Kept only while the\nbooking is cancelled.",
                                "$ref": "#\/components\/schemas\/NotGoingAheadReason"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "hire_type_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "attendee_count": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "minimum": 0,
                        "maximum": 100000
                    },
                    "is_private": {
                        "type": "boolean"
                    },
                    "notes_for_hirer": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 5000
                    },
                    "internal_notes": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 5000
                    }
                },
                "title": "UpdateBookingRequest"
            },
            "VatRate": {
                "type": "string",
                "description": "How VAT applies to something a hall charges for. Note that Zero, Exempt and Outside the scope all work out at no VAT and are nonetheless three different things in law, so they are three options here and must print differently on an invoice.  The percentages below are today's rates. They are never read when reprinting an old invoice: the rate that applied is frozen onto each invoice line when the line is created, because the standard rate has moved three times since 2008 and a 2026 invoice must not be reprinted at a 2030 rate.\n",
                "enum": [
                    "standard",
                    "reduced",
                    "zero",
                    "exempt",
                    "outside_scope"
                ],
                "title": "VatRate"
            },
            "VenueResource": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "phone": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "website": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "address": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "county": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "timezone": {
                        "type": "string",
                        "description": "The time zone every time in this API is given in.",
                        "const": "Europe\/London"
                    },
                    "currency": {
                        "type": "string",
                        "description": "Every amount in this API is in this currency.",
                        "const": "GBP"
                    },
                    "booking_buffer_minutes": {
                        "type": "integer",
                        "description": "Minutes the hall keeps free between one hire and the next."
                    },
                    "links": {
                        "type": "object",
                        "properties": {
                            "public_calendar": {
                                "type": "string"
                            },
                            "enquiry_form": {
                                "type": "string"
                            }
                        },
                        "required": [
                            "public_calendar",
                            "enquiry_form"
                        ]
                    }
                },
                "required": [
                    "id",
                    "name",
                    "email",
                    "phone",
                    "website",
                    "address",
                    "county",
                    "timezone",
                    "currency",
                    "booking_buffer_minutes",
                    "links"
                ],
                "title": "VenueResource"
            }
        },
        "responses": {
            "AuthenticationException": {
                "description": "Unauthenticated",
                "content": {
                    "application\/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "message": {
                                    "type": "string",
                                    "description": "Error overview."
                                }
                            },
                            "required": [
                                "message"
                            ]
                        }
                    }
                }
            },
            "ValidationException": {
                "description": "Validation error",
                "content": {
                    "application\/json": {
                        "schema": {
                            "type": "object",
                            "properties": {
                                "message": {
                                    "type": "string",
                                    "description": "Errors overview."
                                },
                                "errors": {
                                    "type": "object",
                                    "description": "A detailed description of each field that failed validation.",
                                    "additionalProperties": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "required": [
                                "message",
                                "errors"
                            ]
                        }
                    }
                }
            }
        }
    },
    "tags": [
        {
            "name": "Hall",
            "description": "The hall itself: its name, address and how to get in touch."
        },
        {
            "name": "Rooms",
            "description": "The rooms that can be booked."
        },
        {
            "name": "Bookings",
            "description": "The diary: what is booked, when a room is free, and taking a booking."
        },
        {
            "name": "What's on",
            "description": "The bookings the hall advertises publicly, for a What's on page."
        },
        {
            "name": "Closures",
            "description": "Days the hall is shut, such as bank holidays or building work."
        },
        {
            "name": "Hirers",
            "description": "The people and groups who hire the hall."
        },
        {
            "name": "Invoices",
            "description": "Invoices and the payments made against them."
        },
        {
            "name": "Prices",
            "description": "Hire types, rate cards and extras."
        }
    ],
    "webhooks": {
        "booking.created": {
            "post": {
                "summary": "booking.created",
                "description": "A booking was added: on the website, in the app, through this API, from the enquiry form or by Hallie. `data` is the booking, as `GET \/bookings\/{id}` returns it.",
                "operationId": "webhook.booking.created",
                "parameters": [
                    {
                        "name": "X-VHH-Event",
                        "in": "header",
                        "required": true,
                        "description": "The event, as in the body.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Delivery",
                        "in": "header",
                        "required": true,
                        "description": "This message's id. Keep it, and ignore a message you have already handled.",
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "X-VHH-Timestamp",
                        "in": "header",
                        "required": true,
                        "description": "When it was sent, in Unix seconds.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Signature",
                        "in": "header",
                        "required": true,
                        "description": "`v1=` and the HMAC-SHA256, in hex, of the timestamp, a full stop and the raw body, keyed with the signing secret.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "This message. The same as the X-VHH-Delivery header; a repeat carries the same id."
                                    },
                                    "event": {
                                        "type": "string",
                                        "const": "booking.created"
                                    },
                                    "hall_id": {
                                        "type": "integer",
                                        "description": "The hall the change happened in."
                                    },
                                    "occurred_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "data": {
                                        "$ref": "#\/components\/schemas\/BookingResource"
                                    }
                                },
                                "required": [
                                    "id",
                                    "event",
                                    "hall_id",
                                    "occurred_at",
                                    "data"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Received. Any 2xx answer within 10 seconds will do; do slow work afterwards."
                    }
                }
            }
        },
        "booking.updated": {
            "post": {
                "summary": "booking.updated",
                "description": "Something about a booking changed: its time, room, status, title and so on. `data` is the booking as it is now.",
                "operationId": "webhook.booking.updated",
                "parameters": [
                    {
                        "name": "X-VHH-Event",
                        "in": "header",
                        "required": true,
                        "description": "The event, as in the body.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Delivery",
                        "in": "header",
                        "required": true,
                        "description": "This message's id. Keep it, and ignore a message you have already handled.",
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "X-VHH-Timestamp",
                        "in": "header",
                        "required": true,
                        "description": "When it was sent, in Unix seconds.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Signature",
                        "in": "header",
                        "required": true,
                        "description": "`v1=` and the HMAC-SHA256, in hex, of the timestamp, a full stop and the raw body, keyed with the signing secret.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "This message. The same as the X-VHH-Delivery header; a repeat carries the same id."
                                    },
                                    "event": {
                                        "type": "string",
                                        "const": "booking.updated"
                                    },
                                    "hall_id": {
                                        "type": "integer",
                                        "description": "The hall the change happened in."
                                    },
                                    "occurred_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "data": {
                                        "$ref": "#\/components\/schemas\/BookingResource"
                                    }
                                },
                                "required": [
                                    "id",
                                    "event",
                                    "hall_id",
                                    "occurred_at",
                                    "data"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Received. Any 2xx answer within 10 seconds will do; do slow work afterwards."
                    }
                }
            }
        },
        "booking.cancelled": {
            "post": {
                "summary": "booking.cancelled",
                "description": "A booking was cancelled or declined. `data` is the booking, with `status` of `cancelled` and, if the hall said, `not_going_ahead_reason`.",
                "operationId": "webhook.booking.cancelled",
                "parameters": [
                    {
                        "name": "X-VHH-Event",
                        "in": "header",
                        "required": true,
                        "description": "The event, as in the body.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Delivery",
                        "in": "header",
                        "required": true,
                        "description": "This message's id. Keep it, and ignore a message you have already handled.",
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "X-VHH-Timestamp",
                        "in": "header",
                        "required": true,
                        "description": "When it was sent, in Unix seconds.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Signature",
                        "in": "header",
                        "required": true,
                        "description": "`v1=` and the HMAC-SHA256, in hex, of the timestamp, a full stop and the raw body, keyed with the signing secret.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "This message. The same as the X-VHH-Delivery header; a repeat carries the same id."
                                    },
                                    "event": {
                                        "type": "string",
                                        "const": "booking.cancelled"
                                    },
                                    "hall_id": {
                                        "type": "integer",
                                        "description": "The hall the change happened in."
                                    },
                                    "occurred_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "data": {
                                        "$ref": "#\/components\/schemas\/BookingResource"
                                    }
                                },
                                "required": [
                                    "id",
                                    "event",
                                    "hall_id",
                                    "occurred_at",
                                    "data"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Received. Any 2xx answer within 10 seconds will do; do slow work afterwards."
                    }
                }
            }
        },
        "booking.deleted": {
            "post": {
                "summary": "booking.deleted",
                "description": "A booking was deleted outright. It no longer exists, so `data` holds only its `id`.",
                "operationId": "webhook.booking.deleted",
                "parameters": [
                    {
                        "name": "X-VHH-Event",
                        "in": "header",
                        "required": true,
                        "description": "The event, as in the body.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Delivery",
                        "in": "header",
                        "required": true,
                        "description": "This message's id. Keep it, and ignore a message you have already handled.",
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "X-VHH-Timestamp",
                        "in": "header",
                        "required": true,
                        "description": "When it was sent, in Unix seconds.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Signature",
                        "in": "header",
                        "required": true,
                        "description": "`v1=` and the HMAC-SHA256, in hex, of the timestamp, a full stop and the raw body, keyed with the signing secret.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "This message. The same as the X-VHH-Delivery header; a repeat carries the same id."
                                    },
                                    "event": {
                                        "type": "string",
                                        "const": "booking.deleted"
                                    },
                                    "hall_id": {
                                        "type": "integer",
                                        "description": "The hall the change happened in."
                                    },
                                    "occurred_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "id": {
                                                "type": "integer",
                                                "examples": [
                                                    9613
                                                ]
                                            }
                                        },
                                        "required": [
                                            "id"
                                        ]
                                    }
                                },
                                "required": [
                                    "id",
                                    "event",
                                    "hall_id",
                                    "occurred_at",
                                    "data"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Received. Any 2xx answer within 10 seconds will do; do slow work afterwards."
                    }
                }
            }
        },
        "invoice.created": {
            "post": {
                "summary": "invoice.created",
                "description": "An invoice was raised. `data` is the invoice, as `GET \/invoices\/{id}` returns it.",
                "operationId": "webhook.invoice.created",
                "parameters": [
                    {
                        "name": "X-VHH-Event",
                        "in": "header",
                        "required": true,
                        "description": "The event, as in the body.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Delivery",
                        "in": "header",
                        "required": true,
                        "description": "This message's id. Keep it, and ignore a message you have already handled.",
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "X-VHH-Timestamp",
                        "in": "header",
                        "required": true,
                        "description": "When it was sent, in Unix seconds.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Signature",
                        "in": "header",
                        "required": true,
                        "description": "`v1=` and the HMAC-SHA256, in hex, of the timestamp, a full stop and the raw body, keyed with the signing secret.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "This message. The same as the X-VHH-Delivery header; a repeat carries the same id."
                                    },
                                    "event": {
                                        "type": "string",
                                        "const": "invoice.created"
                                    },
                                    "hall_id": {
                                        "type": "integer",
                                        "description": "The hall the change happened in."
                                    },
                                    "occurred_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "data": {
                                        "$ref": "#\/components\/schemas\/InvoiceResource"
                                    }
                                },
                                "required": [
                                    "id",
                                    "event",
                                    "hall_id",
                                    "occurred_at",
                                    "data"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Received. Any 2xx answer within 10 seconds will do; do slow work afterwards."
                    }
                }
            }
        },
        "invoice.sent": {
            "post": {
                "summary": "invoice.sent",
                "description": "An invoice was sent to the hirer.",
                "operationId": "webhook.invoice.sent",
                "parameters": [
                    {
                        "name": "X-VHH-Event",
                        "in": "header",
                        "required": true,
                        "description": "The event, as in the body.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Delivery",
                        "in": "header",
                        "required": true,
                        "description": "This message's id. Keep it, and ignore a message you have already handled.",
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "X-VHH-Timestamp",
                        "in": "header",
                        "required": true,
                        "description": "When it was sent, in Unix seconds.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Signature",
                        "in": "header",
                        "required": true,
                        "description": "`v1=` and the HMAC-SHA256, in hex, of the timestamp, a full stop and the raw body, keyed with the signing secret.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "This message. The same as the X-VHH-Delivery header; a repeat carries the same id."
                                    },
                                    "event": {
                                        "type": "string",
                                        "const": "invoice.sent"
                                    },
                                    "hall_id": {
                                        "type": "integer",
                                        "description": "The hall the change happened in."
                                    },
                                    "occurred_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "data": {
                                        "$ref": "#\/components\/schemas\/InvoiceResource"
                                    }
                                },
                                "required": [
                                    "id",
                                    "event",
                                    "hall_id",
                                    "occurred_at",
                                    "data"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Received. Any 2xx answer within 10 seconds will do; do slow work afterwards."
                    }
                }
            }
        },
        "invoice.paid": {
            "post": {
                "summary": "invoice.paid",
                "description": "An invoice was paid in full.",
                "operationId": "webhook.invoice.paid",
                "parameters": [
                    {
                        "name": "X-VHH-Event",
                        "in": "header",
                        "required": true,
                        "description": "The event, as in the body.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Delivery",
                        "in": "header",
                        "required": true,
                        "description": "This message's id. Keep it, and ignore a message you have already handled.",
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "X-VHH-Timestamp",
                        "in": "header",
                        "required": true,
                        "description": "When it was sent, in Unix seconds.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Signature",
                        "in": "header",
                        "required": true,
                        "description": "`v1=` and the HMAC-SHA256, in hex, of the timestamp, a full stop and the raw body, keyed with the signing secret.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "This message. The same as the X-VHH-Delivery header; a repeat carries the same id."
                                    },
                                    "event": {
                                        "type": "string",
                                        "const": "invoice.paid"
                                    },
                                    "hall_id": {
                                        "type": "integer",
                                        "description": "The hall the change happened in."
                                    },
                                    "occurred_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "data": {
                                        "$ref": "#\/components\/schemas\/InvoiceResource"
                                    }
                                },
                                "required": [
                                    "id",
                                    "event",
                                    "hall_id",
                                    "occurred_at",
                                    "data"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Received. Any 2xx answer within 10 seconds will do; do slow work afterwards."
                    }
                }
            }
        },
        "payment.recorded": {
            "post": {
                "summary": "payment.recorded",
                "description": "A payment was recorded against an invoice, by card, cash, cheque or bank transfer. `data` is the payment.",
                "operationId": "webhook.payment.recorded",
                "parameters": [
                    {
                        "name": "X-VHH-Event",
                        "in": "header",
                        "required": true,
                        "description": "The event, as in the body.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Delivery",
                        "in": "header",
                        "required": true,
                        "description": "This message's id. Keep it, and ignore a message you have already handled.",
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "X-VHH-Timestamp",
                        "in": "header",
                        "required": true,
                        "description": "When it was sent, in Unix seconds.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Signature",
                        "in": "header",
                        "required": true,
                        "description": "`v1=` and the HMAC-SHA256, in hex, of the timestamp, a full stop and the raw body, keyed with the signing secret.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "This message. The same as the X-VHH-Delivery header; a repeat carries the same id."
                                    },
                                    "event": {
                                        "type": "string",
                                        "const": "payment.recorded"
                                    },
                                    "hall_id": {
                                        "type": "integer",
                                        "description": "The hall the change happened in."
                                    },
                                    "occurred_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "data": {
                                        "$ref": "#\/components\/schemas\/PaymentResource"
                                    }
                                },
                                "required": [
                                    "id",
                                    "event",
                                    "hall_id",
                                    "occurred_at",
                                    "data"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Received. Any 2xx answer within 10 seconds will do; do slow work afterwards."
                    }
                }
            }
        },
        "ping": {
            "post": {
                "summary": "ping",
                "description": "Sent only by the Send a test button. `data.message` says so.",
                "operationId": "webhook.ping",
                "parameters": [
                    {
                        "name": "X-VHH-Event",
                        "in": "header",
                        "required": true,
                        "description": "The event, as in the body.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Delivery",
                        "in": "header",
                        "required": true,
                        "description": "This message's id. Keep it, and ignore a message you have already handled.",
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "X-VHH-Timestamp",
                        "in": "header",
                        "required": true,
                        "description": "When it was sent, in Unix seconds.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-VHH-Signature",
                        "in": "header",
                        "required": true,
                        "description": "`v1=` and the HMAC-SHA256, in hex, of the timestamp, a full stop and the raw body, keyed with the signing secret.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "This message. The same as the X-VHH-Delivery header; a repeat carries the same id."
                                    },
                                    "event": {
                                        "type": "string",
                                        "const": "ping"
                                    },
                                    "hall_id": {
                                        "type": "integer",
                                        "description": "The hall the change happened in."
                                    },
                                    "occurred_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "message": {
                                                "type": "string"
                                            }
                                        },
                                        "required": [
                                            "message"
                                        ]
                                    }
                                },
                                "required": [
                                    "id",
                                    "event",
                                    "hall_id",
                                    "occurred_at",
                                    "data"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Received. Any 2xx answer within 10 seconds will do; do slow work afterwards."
                    }
                }
            }
        }
    }
}