# Gastmanager Booking Portal API A read-only JSON API for AI agents to discover availability and pricing of a Gastmanager booking portal. The agent can NOT create bookings, take payments, or write any data — use the interactive booking portal for that. Instead, every priced response carries a `booking_url` deeplink: hand it to the human to finish the booking. The example URLs below are live — fetch them as-is. ## Available Portals ### Camping (`camping`) * Config: GET https://book.schwarzesroessl.at/booking/api/v1/portals/camping * Categories: GET https://book.schwarzesroessl.at/booking/api/v1/portals/camping/categories?from=2026-08-01&to=2026-08-08&guests[]=adult&guests[]=adult * Calendar: GET https://book.schwarzesroessl.at/booking/api/v1/portals/camping/categories/CATEGORY/availability?from=2026-08-01&to=2026-08-31 * Last minute: GET https://book.schwarzesroessl.at/booking/api/v1/portals/camping/last-minute * Deals: GET https://book.schwarzesroessl.at/booking/api/v1/portals/camping/deals?from=2026-08-01&to=2026-08-08 * Quote: POST https://book.schwarzesroessl.at/booking/api/v1/portals/camping/quote * Book (human): https://book.schwarzesroessl.at/booking/camping ### Hotel (`hotel`) * Config: GET https://book.schwarzesroessl.at/booking/api/v1/portals/hotel * Categories: GET https://book.schwarzesroessl.at/booking/api/v1/portals/hotel/categories?from=2026-08-01&to=2026-08-08&guests[]=adult&guests[]=adult * Calendar: GET https://book.schwarzesroessl.at/booking/api/v1/portals/hotel/categories/CATEGORY/availability?from=2026-08-01&to=2026-08-31 * Last minute: GET https://book.schwarzesroessl.at/booking/api/v1/portals/hotel/last-minute * Deals: GET https://book.schwarzesroessl.at/booking/api/v1/portals/hotel/deals?from=2026-08-01&to=2026-08-08 * Quote: POST https://book.schwarzesroessl.at/booking/api/v1/portals/hotel/quote * Book (human): https://book.schwarzesroessl.at/booking/hotel ### Schwarzes Rössl (`schwarzes-roessl`) * Config: GET https://book.schwarzesroessl.at/booking/api/v1/portals/schwarzes-roessl * Categories: GET https://book.schwarzesroessl.at/booking/api/v1/portals/schwarzes-roessl/categories?from=2026-08-01&to=2026-08-08&guests[]=adult&guests[]=adult * Calendar: GET https://book.schwarzesroessl.at/booking/api/v1/portals/schwarzes-roessl/categories/CATEGORY/availability?from=2026-08-01&to=2026-08-31 * Last minute: GET https://book.schwarzesroessl.at/booking/api/v1/portals/schwarzes-roessl/last-minute * Deals: GET https://book.schwarzesroessl.at/booking/api/v1/portals/schwarzes-roessl/deals?from=2026-08-01&to=2026-08-08 * Quote: POST https://book.schwarzesroessl.at/booking/api/v1/portals/schwarzes-roessl/quote * Book (human): https://book.schwarzesroessl.at/booking/schwarzes-roessl ## Quickstart Using the portal `camping` as an example: 1. Load the portal config: GET https://book.schwarzesroessl.at/booking/api/v1/portals/camping 2. Check availability for a stay: GET https://book.schwarzesroessl.at/booking/api/v1/portals/camping/categories?from=2026-08-01&to=2026-08-08&guests[]=adult&guests[]=adult 3. Optional — per-day availability calendar of one category (max 62 days, inclusive range; price = single night, not a stay total): GET https://book.schwarzesroessl.at/booking/api/v1/portals/camping/categories/STANDARD/availability?from=2026-08-01&to=2026-08-31 4. Optional — current last-minute offers (short free gaps, price for two adults; `booking_url` carries the discount token if configured): GET https://book.schwarzesroessl.at/booking/api/v1/portals/camping/last-minute 5. Optional — special deals for the timeframe (bookable packages plus alternatives at slightly shifted timeframes, each with a `selection=c.X:p.Y` deeplink): GET https://book.schwarzesroessl.at/booking/api/v1/portals/camping/deals?from=2026-08-01&to=2026-08-08&guests[]=adult&guests[]=adult 6. Get a price preview: POST https://book.schwarzesroessl.at/booking/api/v1/portals/camping/quote Content-Type: application/json Body: { "timeframe": {...}, "guests_per_slot": [...], "selection": [...] } ## Example Responses `GET …/portals/camping` → { "data": { "identifier": "camping", "name": "Summer Campsite", "languages": ["de", "en"], "payment_methods": [{ "id": 1 }], "max_accommodations": 3, "has_extras": true, "blocked_periods": [], "url": "https://book.schwarzesroessl.at/booking/camping" } } `GET …/categories?from=2026-08-01&to=2026-08-08` → the `available` boolean is the availability flag; `booking_url` opens that category with the search prefilled. Categories backed by a site plan carry a `plot_plan` object with that category's accommodations and the group areas containing them (`mode: "exact_booking"` means a concrete pitch can be booked, so `accommodation_id`/`group_id` may be used in quote selections; `"show_only"` is informational); it is `null` otherwise: { "data": [ { "id": 12, "external_id": "STANDARD", "name": "Standard Pitch", "max_guests": 6, "available": true, "selection_options": [ { "id": null, "type": "default", "name": "Standard" } ], "extras": [ { "id": 42, "external_id": "BREAKFAST", "type": "consumable", "name": "Breakfast" } ], "price": { "amount": 18000, "currency": "EUR", "gross": 180.0 }, "plot_plan": { "mode": "exact_booking", "map": { "id": 5, "name": "Site overview", "image_url": "https://…" }, "accommodations": [ { "id": 101, "name": "Pitch 1", "available": true }, { "id": 102, "name": "Pitch 2", "available": false } ], "groups": [ { "id": 7, "name": "Lakeside", "available": true, "accommodation_ids": [101, 102] } ] }, "booking_url": "https://book.schwarzesroessl.at/booking/camping/category/12?guests=2&timeframe=2026-08-01:2026-08-08" } ] } `GET …/categories/STANDARD/availability?from=2026-08-01&to=2026-08-03` → one entry per day; `available_accommodations` (id = booking key, name = site-plan label) and `available_group_ids` are null for categories without a site plan: { "data": { "category": { "id": 12, "external_id": "STANDARD" }, "from": "2026-08-01", "to": "2026-08-03", "guests": { "people": ["adult", "adult"], "dogs": 0, "vehicles": [] }, "days": [ { "date": "2026-08-01", "available": true, "blocked": false, "no_arrival": false, "price_per_night": { "amount": 4500, "currency": "EUR", "gross": 45.0 }, "available_accommodations": [ { "id": 101, "name": "Pitch 1" }, { "id": 102, "name": "Pitch 2" } ], "available_group_ids": [7] }, { "date": "2026-08-02", "available": false, "blocked": false, "no_arrival": false, "price_per_night": null, "available_accommodations": [], "available_group_ids": [] } ] } } `POST …/quote` → { "data": { "timeframe": { "from": "2026-08-01", "to": "2026-08-08" }, "total": { "amount": 19400, "currency": "EUR", "gross": 194.0 }, "net_total": { "amount": 17636, "currency": "EUR", "gross": 176.36 }, "taxes": { "amount": 1764, "currency": "EUR", "gross": 17.64 }, "tax_rates": [ { "rate": 10, "taxes": { "amount": 1764, "currency": "EUR", "gross": 17.64 } } ], "items": [ { "name": "Standard Pitch", "type": "accommodation", "amount": 7, "total": { "amount": 18000, "currency": "EUR", "gross": 180.0 }, "unit_price": { "amount": 18000, "currency": "EUR", "gross": 180.0 } } ], "booking_url": "https://book.schwarzesroessl.at/booking/camping?guests=2:10|1&selection=c.STANDARD&timeframe=2026-08-01:2026-08-08" } } ## Defaults Categories endpoint query-params default to: * `guests=adult,adult` (two adults) * `dogs=0` * `vehicles=[]` ## Selection JSON Format The `selection` array contains one entry per selected accommodation (`mode: "category"`) plus optional booking-wide entries (`mode: "global"`). The order of `mode: "category"` entries MUST match the order of `guests_per_slot` 1:1 — the first category-mode selection is for the first guests slot, etc. `global` entries are not slot-bound; they apply to the booking as a whole. Fields per selection item: * `mode` (required) — `"category"` or `"global"`. * `category_id` (required for `mode: "category"`) — internal accommodation-group ID from the categories endpoint (the `id` field, not `external_id`). * `accommodation_id` (optional) — lock the selection to a specific accommodation (plot/unit) inside the category. * `group_id` (optional) — lock to a plot-plan area. * `extras` (optional, list) — extras and at most one package for this slot. Each entry has `type`, `id`, optional `modifier` (see "Modifier Kinds" below). At most one entry per slot may have `type: "package"`. Full example body for the quote endpoint: { "timeframe": { "from": "2026-08-01", "to": "2026-08-08" }, "guests_per_slot": [ { "people": ["adult", "adult", 10], "dogs": 1, "vehicles": [1] } ], "selection": [ { "mode": "category", "category_id": 12, "extras": [ { "type": "package", "id": 7 }, { "type": "consumable", "id": 42, "modifier": { "kind": "amount", "value": 14 } } ] }, { "mode": "global", "extras": [ { "type": "service", "id": 99 } ] } ] } ## Modifier Kinds Modifiers configure how an extra is applied. There are six kinds. All datetimes are naive ISO 8601 (no timezone); the server interprets them in Europe/Vienna. * `guests` — apply to specific guests (by 0-based index into the slot's `people` array). All indices must be valid. Example: `{ "kind": "guests", "indices": [0, 2] }` * `amount` — set a positive quantity for the extra. Example: `{ "kind": "amount", "value": 14 }` * `daterange` — only apply on dates in [from, to). `from` must be strictly before `to`. Example: `{ "kind": "daterange", "from": "2026-08-02", "to": "2026-08-05" }` * `date_amounts` — different quantity per date. Example: `{ "kind": "date_amounts", "entries": [ { "date": "2026-08-02", "amount": 3 }, { "date": "2026-08-03", "amount": 5 } ] }` * `datetime` — pin to a specific naive datetime. Example: `{ "kind": "datetime", "value": "2026-08-01T18:30:00" }` * `time_window` — naive `start` plus `HH:MM:SS` `duration`. Example: `{ "kind": "time_window", "start": "2026-08-01T18:00:00", "duration": "02:00:00" }` ## Booking Links (deeplink format) The API is read-only, so the highest-value output is a prefilled `booking_url` you can hand to the human. Every category entry and the quote response already contain one. If you want to build a link yourself, it is the interactive portal URL plus these query params: * `timeframe=FROM:TO` — ISO dates, e.g. `2026-08-01:2026-08-08`. Variants: `FROM:+N` (N nights), `…|2` (exactly 2 nights within the range), `…|2-3` (2–3 nights), `!FROM` / `!TO` (fixed arrival/departure), `…|a3` (also search ±3 nights for alternatives). * `guests=ADULTS[:CHILDAGES][,Ddogs][|Vtype,…]`, slots separated by `+`. Examples: `2` (two adults); `2:4,5` (two adults + children aged 4 and 5); `2:4,5,D2|V1` (… + 2 dogs + one vehicle of type 1); `2+3` (slot 1 two adults, slot 2 three adults). * `selection=c.EXTERNAL_ID[.ACCOMMODATION_ID][:p.PKG,x.EXTRA~MODIFIER]`, slots separated by `+`. `c.` = category (by `external_id`), `p.` = package, `x.` = extra. Example: `c.STANDARD:p.SUMMER-DEAL,x.BREAKFAST~G0,G2+c.DELUXE`. Example deeplink: https://book.schwarzesroessl.at/booking/camping/category/12?guests=2:10|1&timeframe=2026-08-01:2026-08-08 ## Error Codes All errors follow the same shape: `{ "error": { "code": ..., "message": ..., "field"?: ..., "retry_after"?: ... } }` * `invalid_param` (HTTP 400) — request validation failed; `field` names the offending parameter. * `not_found` (HTTP 404) — portal or resource not found (also used for inactive portals to avoid leaking existence). * `rate_limited` (HTTP 429) — too many requests from this IP; the `retry_after` field tells you how many seconds to wait. * `internal` (HTTP 500) — unexpected server error. ## Rate Limits Per-IP, 60 second sliding window: * Portal endpoint — 60 requests/min * Categories endpoint — 30 requests/min * Availability calendar — 10 requests/min * Last-minute endpoint — 30 requests/min * Deals endpoint — 20 requests/min * Quote endpoint — 20 requests/min * Discovery (openapi/llms/ai-plugin) — 120 requests/min * Global cap across all endpoints — 100 requests/min No key is required. Integrators with an issued API key can send it as `X-Api-Key` header to get higher per-key limits on the calendar, last-minute and deals endpoints. ## Conventions * Dates are ISO 8601 (`YYYY-MM-DD`). * Datetimes are naive ISO 8601 (`YYYY-MM-DDTHH:MM:SS`) — no timezone suffix; interpreted in Europe/Vienna server-side. * Money is reported as integer minor unit (`amount`, e.g. cents) plus a decimal convenience field (`gross`). * No authentication required — rate-limited per source IP. * The client is identified by the request host (`.gastmanager.`); there is no client path segment. ## Discovery * OpenAPI spec: https://book.schwarzesroessl.at/booking/api/v1/openapi.json * AI-plugin manifest: https://book.schwarzesroessl.at/booking/.well-known/ai-plugin.json * This file: https://book.schwarzesroessl.at/booking/llms.txt