{"openapi":"3.1.0","info":{"title":"tourpingo public API","version":"0.1.0","description":"For websites: trips, live prices, availability, bookings and a site's own pages, with the site's publishable key. The guides are at https://docs.stg.vacationpackagesoman.com."},"paths":{"/v1/public/storefront":{"get":{"tags":["public"],"operationId":"public.getStorefront","parameters":[],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicStorefront","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicStorefront"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The storefront the publishable key belongs to, with its languages, currencies and rounding.","summary":"The storefront"}},"/v1/public/page":{"get":{"tags":["public"],"operationId":"public.getPage","parameters":[{"name":"slug","in":"query","schema":{"type":"string","description":"The page's address, e.g. `about`."},"required":true,"description":"The page's address, e.g. `about`."},{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`.","pattern":"^[a-z]{2}$"},"required":false,"description":"The language wanted, e.g. `fr`."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicPage","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPage"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"A published page of this storefront by its `slug` (e.g. `about` or `guides/muscat`), in the language asked for or the storefront's default.","summary":"A page"}},"/v1/public/pages":{"get":{"tags":["public"],"operationId":"public.listPages","parameters":[],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicPageList","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPageList"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"This storefront's published pages, for its sitemap: each by `slug`, but those whose default language asks search engines not to list them (`noindex`).","summary":"The pages"}},"/v1/public/menus":{"get":{"tags":["public"],"operationId":"public.getMenus","parameters":[{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`.","pattern":"^[a-z]{2}$"},"required":false,"description":"The language wanted, e.g. `fr`."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicMenu"}}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The header and footer menus, labelled in the language asked for or the storefront's default, each link an address. Items whose page or trip isn't published are left out.","summary":"The menus"}},"/v1/public/products":{"get":{"tags":["public"],"operationId":"public.listProducts","parameters":[{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`: trips published in it are in it, the rest in English."},"required":false,"description":"The language wanted, e.g. `fr`: trips published in it are in it, the rest in English."},{"name":"q","in":"query","schema":{"type":"string","description":"What the traveller typed, e.g. `wadi shab`: typos are forgiven (`wadi shap` finds it too), a trip's places, categories and city count as well as its title and summary, and the best matches come first. When search is down, only trips with the words in their title or summary are found."},"required":false,"description":"What the traveller typed, e.g. `wadi shab`: typos are forgiven (`wadi shap` finds it too), a trip's places, categories and city count as well as its title and summary, and the best matches come first. When search is down, only trips with the words in their title or summary are found."},{"name":"template","in":"query","schema":{"type":"string","description":"One or more templates, comma-separated, e.g. `day_tour,activity`."},"required":false,"description":"One or more templates, comma-separated, e.g. `day_tour,activity`."},{"name":"city","in":"query","schema":{"type":"string","description":"One or more city slugs, comma-separated, e.g. `muscat`: only trips that start in one of them."},"required":false,"description":"One or more city slugs, comma-separated, e.g. `muscat`: only trips that start in one of them."},{"name":"region","in":"query","schema":{"type":"string","description":"One or more region slugs, comma-separated, e.g. `ad-dakhiliyah`: only trips that start in a city of one of them."},"required":false,"description":"One or more region slugs, comma-separated, e.g. `ad-dakhiliyah`: only trips that start in a city of one of them."},{"name":"place","in":"query","schema":{"type":"string","description":"One or more place slugs, comma-separated, e.g. `wadi-shab`: only trips that start at one of them or visit one (a stop or a night's stay)."},"required":false,"description":"One or more place slugs, comma-separated, e.g. `wadi-shab`: only trips that start at one of them or visit one (a stop or a night's stay)."},{"name":"category","in":"query","schema":{"type":"string","description":"One or more category slugs, comma-separated, e.g. `desert-safari,boat-dolphin`: only trips in one of them."},"required":false,"description":"One or more category slugs, comma-separated, e.g. `desert-safari,boat-dolphin`: only trips in one of them."},{"name":"limit","in":"query","schema":{"type":"string","description":"Trips per page, 20 by default.","title":"between(1, 100)"},"required":false,"description":"Trips per page, 20 by default."},{"name":"cursor","in":"query","schema":{"type":"string","description":"The `next_cursor` of the page before."},"required":false,"description":"The `next_cursor` of the page before."},{"name":"currency","in":"query","schema":{"type":"string","description":"The currency for `from_price`, e.g. `OMR`: one of the storefront's currencies, else its display currency."},"required":false,"description":"The currency for `from_price`, e.g. `OMR`: one of the storefront's currencies, else its display currency."},{"name":"country","in":"query","schema":{"type":"string","description":"One or more country codes, comma-separated, e.g. `om`: only trips that start in one of them."},"required":false,"description":"One or more country codes, comma-separated, e.g. `om`: only trips that start in one of them."},{"name":"duration","in":"query","schema":{"type":"string","description":"One or more lengths, comma-separated: `up_to_3h`, `3h_to_6h`, `full_day` (up to a day) or `multi_day`, e.g. `up_to_3h,3h_to_6h` for up to half a day."},"required":false,"description":"One or more lengths, comma-separated: `up_to_3h`, `3h_to_6h`, `full_day` (up to a day) or `multi_day`, e.g. `up_to_3h,3h_to_6h` for up to half a day."},{"name":"seller","in":"query","schema":{"type":"string","description":"One or more sellers' slugs, comma-separated, e.g. `vpo`: on a marketplace, only their trips."},"required":false,"description":"One or more sellers' slugs, comma-separated, e.g. `vpo`: on a marketplace, only their trips."},{"name":"via","in":"query","schema":{"type":"string","description":"One or more place slugs, comma-separated, e.g. `wadi-shab`: only trips whose stops visit one of them (a trip's `visits`)."},"required":false,"description":"One or more place slugs, comma-separated, e.g. `wadi-shab`: only trips whose stops visit one of them (a trip's `visits`)."},{"name":"price","in":"query","schema":{"type":"string","description":"`shown` for trips with a `from_price`, `ask` for those without (ask the seller), or both, comma-separated."},"required":false,"description":"`shown` for trips with a `from_price`, `ask` for those without (ask the seller), or both, comma-separated."},{"name":"travellers","in":"query","schema":{"type":"string","description":"A group size, 1 to 50: each card then has `group_price`, what that many pay, and `next_group_price`, what one more would.","title":"between(1, 50)"},"required":false,"description":"A group size, 1 to 50: each card then has `group_price`, what that many pay, and `next_group_price`, what one more would."},{"name":"max_price","in":"query","schema":{"type":"string","description":"The most one traveller pays, in minor units of the list's currency: each one's share of `group_price` with `travellers`, else the `from_price`. Trips with no such price are left out.","title":"between(0, 1000000000)"},"required":false,"description":"The most one traveller pays, in minor units of the list's currency: each one's share of `group_price` with `travellers`, else the `from_price`. Trips with no such price are left out."},{"name":"sort","in":"query","schema":{"type":"string","enum":["recommended","price","rating","duration"],"description":"`recommended` (the default: the best matches for `q` first, else the newest), `price` (per person for `travellers`, else the from price; unpriced last), `rating` (the best score on the seller's own site first, then the most reviews; unrated last) or `duration` (shortest first)."},"required":false,"description":"`recommended` (the default: the best matches for `q` first, else the newest), `price` (per person for `travellers`, else the from price; unpriced last), `rating` (the best score on the seller's own site first, then the most reviews; unrated last) or `duration` (shortest first)."},{"name":"facets","in":"query","schema":{"type":"string","enum":["true","false"],"description":"`true` adds `facets`: for each filter, how many trips each value would show with the other filters kept."},"required":false,"description":"`true` adds `facets`: for each filter, how many trips each value would show with the other filters kept."},{"name":"home","in":"query","schema":{"type":"string","enum":["true","false"],"description":"`true` for the home's trips rail (issue #603): on a marketplace, the trips platform staff feature come first, in their order, then the rest in the list's order, and the trips hidden from the home are left out; they stay in every other list and in search. A direct storefront's list is the same either way."},"required":false,"description":"`true` for the home's trips rail (issue #603): on a marketplace, the trips platform staff feature come first, in their order, then the rest in the list's order, and the trips hidden from the home are left out; they stay in every other list and in search. A direct storefront's list is the same either way."},{"name":"collection","in":"query","schema":{"type":"string","description":"A collection's slug from `GET /v1/public/collections` (issue #603): on a marketplace, that collection's trips in the order platform staff put them, without those hidden from the home; none when it isn't showing today, and none on a direct storefront. Takes the place of `home`."},"required":false,"description":"A collection's slug from `GET /v1/public/collections` (issue #603): on a marketplace, that collection's trips in the order platform staff put them, without those hidden from the home; none when it isn't showing today, and none on a direct storefront. Takes the place of `home`."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"TripList","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripList"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Published trips of the storefront's operator, or on a marketplace every seller's trips approved for the network, newest first, a page at a time, with `total` for the filters. Each in `locale` when published in it, else English, with its `seller`, the places it visits, its pickup, and its `from_price`. With `q`, the words go through search: typos are forgiven and the best matches come first. The search page's parameters (`q`, `duration`, `seller`, `via`, `price`, `travellers`, `max_price`, `sort`, `facets`) look through the first trips the other filters find, price each for the group, and count each filter's values.","summary":"The storefront's trips"}},"/v1/public/search":{"get":{"tags":["public"],"operationId":"public.searchProducts","parameters":[{"name":"q","in":"query","schema":{"type":"string","description":"What the traveller typed, e.g. `wadi shab`; typos are forgiven. Leave it out, or empty, for every trip."},"required":false,"description":"What the traveller typed, e.g. `wadi shab`; typos are forgiven. Leave it out, or empty, for every trip."},{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`: trips published in it are found in it, the rest in English."},"required":false,"description":"The language wanted, e.g. `fr`: trips published in it are found in it, the rest in English."},{"name":"city","in":"query","schema":{"type":"string","description":"One or more city slugs, comma-separated, e.g. `muscat`: only trips that start in one of them."},"required":false,"description":"One or more city slugs, comma-separated, e.g. `muscat`: only trips that start in one of them."},{"name":"category","in":"query","schema":{"type":"string","description":"One or more category slugs, comma-separated, e.g. `desert-safari`: only trips in one of them."},"required":false,"description":"One or more category slugs, comma-separated, e.g. `desert-safari`: only trips in one of them."},{"name":"template","in":"query","schema":{"type":"string","description":"One or more templates, comma-separated, e.g. `day_tour,activity`."},"required":false,"description":"One or more templates, comma-separated, e.g. `day_tour,activity`."},{"name":"duration_min","in":"query","schema":{"type":"string","description":"Only trips at least this long, in minutes; a day counts 1,440, so `1440` is a day or more.","title":"between(0, 100000)"},"required":false,"description":"Only trips at least this long, in minutes; a day counts 1,440, so `1440` is a day or more."},{"name":"duration_max","in":"query","schema":{"type":"string","description":"Only trips at most this long, in minutes, e.g. `180` for up to 3 hours.","title":"between(0, 100000)"},"required":false,"description":"Only trips at most this long, in minutes, e.g. `180` for up to 3 hours."},{"name":"page","in":"query","schema":{"type":"string","description":"The page, from 1 (the default).","title":"between(1, 1000)"},"required":false,"description":"The page, from 1 (the default)."},{"name":"per_page","in":"query","schema":{"type":"string","description":"Trips per page, 1 to 50; 20 by default.","title":"between(1, 50)"},"required":false,"description":"Trips per page, 1 to 50; 20 by default."},{"name":"currency","in":"query","schema":{"type":"string","description":"The currency for `from_price`, e.g. `OMR`: one of the storefront's currencies, else its display currency."},"required":false,"description":"The currency for `from_price`, e.g. `OMR`: one of the storefront's currencies, else its display currency."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"TripSearch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripSearch"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}},"503":{"description":"SearchUnavailableProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/SearchUnavailableProblem"}}}}},"description":"Published trips of the storefront's operator, or on a marketplace every seller's trips approved for the network, that match `q`, forgiving typos (`wady shab` finds Wadi Shab), filtered by city, category, template and length, a page at a time, with facet counts. Each in `locale` when published in it, else English, with its `seller` and `from_price`. From the search index, which follows changes within seconds; `SearchUnavailable` (503) while the index is down or not set up, never an empty result instead.","summary":"Search the storefront's trips"}},"/v1/public/products/{product}":{"get":{"tags":["public"],"operationId":"public.getProduct","parameters":[{"name":"product","in":"path","schema":{"type":"string","description":"The trip's id or slug; on a marketplace, a slug needs `seller` too."},"required":true,"description":"The trip's id or slug; on a marketplace, a slug needs `seller` too."},{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`: served when the trip is published in it, else English."},"required":false,"description":"The language wanted, e.g. `fr`: served when the trip is published in it, else English."},{"name":"seller","in":"query","schema":{"type":"string","description":"On a marketplace, the seller's slug (a trip's `seller.slug`, e.g. `vpo`), needed to open a trip by a slug two sellers list; a direct storefront ignores it."},"required":false,"description":"On a marketplace, the seller's slug (a trip's `seller.slug`, e.g. `vpo`), needed to open a trip by a slug two sellers list; a direct storefront ignores it."},{"name":"currency","in":"query","schema":{"type":"string","description":"The currency for `from_price` and the options' `pickup_rules`, e.g. `OMR`: one of the storefront's currencies, else its display currency."},"required":false,"description":"The currency for `from_price` and the options' `pickup_rules`, e.g. `OMR`: one of the storefront's currencies, else its display currency."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicTrip","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicTrip"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"A published trip of the storefront's operator, by id or slug: its text in `locale` when published in it, else English (`content_locale` says which), photos, inclusions, and options with their units, cancellation policy and the traveller details they ask. On a marketplace, a trip approved for the network, by id, or by slug with its `seller`'s slug.","summary":"A trip's page"}},"/v1/public/cities":{"get":{"tags":["public"],"operationId":"public.listCities","parameters":[{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`: names are in it when translated, else English."},"required":false,"description":"The language wanted, e.g. `fr`: names are in it when translated, else English."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicCityList","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCityList"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The cities the storefront's published trips start in, each with its region, country, `world_region`, `trip_count` and `seller_count`, named in `locale` when translated, else English. A city only visited on the way isn't listed; its places are, under `/v1/public/places`.","summary":"The storefront's cities"}},"/v1/public/sellers":{"get":{"tags":["public"],"operationId":"public.listSellers","parameters":[{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`: names are in it when translated, else English."},"required":false,"description":"The language wanted, e.g. `fr`: names are in it when translated, else English."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicSellerList","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSellerList"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The operators whose trips the storefront sells, each with the country it is based in and its `trip_count`: on a direct storefront its own operator; on a marketplace every seller with an approved listing. A seller with no trip to show isn't listed.","summary":"The storefront's sellers"}},"/v1/public/categories":{"get":{"tags":["public"],"operationId":"public.listCategories","parameters":[{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`: names are in it when translated, else English."},"required":false,"description":"The language wanted, e.g. `fr`: names are in it when translated, else English."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicCategoryList","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCategoryList"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The platform categories the storefront's published trips are in, each with its `trip_count`, named in `locale` when translated, else English, in the platform's order. A category with no published trip isn't listed.","summary":"The storefront's categories"}},"/v1/public/collections":{"get":{"tags":["public"],"operationId":"public.listCollections","parameters":[{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`: names are in it when translated, else English."},"required":false,"description":"The language wanted, e.g. `fr`: names are in it when translated, else English."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicCollectionList","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicCollectionList"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"On a marketplace, the collections of trips platform staff put together that are showing today (between their first and last day, in UTC) and have trips, in their order, each named in `locale` when written there, else English. Their trips come from `GET /v1/public/products?collection=`. Empty on a direct storefront.","summary":"The marketplace home's collections"}},"/v1/public/places":{"get":{"tags":["public"],"operationId":"public.listPlaces","parameters":[{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`: names are in it when translated, else English."},"required":false,"description":"The language wanted, e.g. `fr`: names are in it when translated, else English."},{"name":"city","in":"query","schema":{"type":"string","description":"One or more city slugs, comma-separated, e.g. `muscat`: only places in one of them."},"required":false,"description":"One or more city slugs, comma-separated, e.g. `muscat`: only places in one of them."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicPlaceList","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPlaceList"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The library places the storefront's published trips start at or visit (a stop or a night's stay), optionally only those in the cities given, by name.","summary":"The storefront's places"}},"/v1/public/places/{place}":{"get":{"tags":["public"],"operationId":"public.getPlace","parameters":[{"name":"place","in":"path","schema":{"type":"string","description":"The place's slug, e.g. `wadi-shab`."},"required":true,"description":"The place's slug, e.g. `wadi-shab`."},{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`: names are in it when translated, else English."},"required":false,"description":"The language wanted, e.g. `fr`: names are in it when translated, else English."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicPlace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPlace"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"A library place by slug, with its text in `locale` when translated (not a draft), else English (`content_locale` says which). Not found unless one of the storefront's published trips starts at or visits it, so the library isn't exposed whole.","summary":"A place's page"}},"/v1/public/quotes":{"post":{"tags":["public"],"operationId":"public.quote","parameters":[],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"A price: what the traveller pays, line by line, in the quote's currency, with the policy and pickup that come with it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/InvalidInputProblem"},{"$ref":"#/components/schemas/QuoteRefusedProblem"}]}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"What the traveller pays for a published trip's option on a date: lines, totals, what's paid on the day, confirmation and the cancellation policy. In the storefront's display currency unless `currency` names another it sells in; rounded its way.","summary":"Quote an option","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicQuoteRequest"}}},"required":true}}},"/v1/public/holds":{"post":{"tags":["public"],"operationId":"public.hold","parameters":[{"name":"idempotency-key","in":"header","schema":{"type":"string","description":"Any unique string the storefront makes per hold, e.g. a UUID, so a retry can't hold the places twice. The same key with the same request answers with the first hold as it stands; with a different request it is a `409 Conflict`. The `attribution` is not compared: a repeat with other tags answers with the first hold and its tags. Keys are kept per storefront.","title":"maxLength(255)","minLength":1,"maxLength":255},"required":false,"description":"Any unique string the storefront makes per hold, e.g. a UUID, so a retry can't hold the places twice. The same key with the same request answers with the first hold as it stands; with a different request it is a `409 Conflict`. The `attribution` is not compared: a repeat with other tags answers with the first hold and its tags. Keys are kept per storefront."}],"security":[{"publishableKey":[]}],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["ref","status","hold_expires_at","start","quote"],"properties":{"ref":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"status":{"type":"string","enum":["on_hold","pending","confirmed","completed","cancelled","rejected","expired"],"description":"Where the booking stands: `on_hold` while checkout holds its places, `pending` until the operator confirms it, `confirmed`, `completed` once the trip is over, or `cancelled`, `rejected` or `expired`."},"hold_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the places go back on sale."},"start":{"type":"string","description":"The start held, e.g. `2026-10-07T08:00`."},"quote":{"allOf":[{"$ref":"#/components/schemas/Quote"}],"description":"The price held, re-quoted as the hold was made: what checkout charges."}},"additionalProperties":false}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/InvalidInputProblem"},{"$ref":"#/components/schemas/QuoteRefusedProblem"}]}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Re-quotes the selection and holds its places on the start for 15 minutes, until checkout. Refused like a quote, with `OnRequest` for an option that is booked on request, which checkout takes directly, and with `SellerUnavailable` on a suspended seller's own site. Send an `Idempotency-Key` so that a retry, or a second click, answers with the same hold instead of holding the places again.","summary":"Hold places","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicHoldRequest"}}},"required":true}}},"/v1/public/holds/{ref}":{"get":{"tags":["public"],"operationId":"public.getHold","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"Hold","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Hold"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"As it stands: `expired` once its time is up, when its places are back on sale.","summary":"A hold"},"patch":{"tags":["public"],"operationId":"public.changeHold","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"Hold","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Hold"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/InvalidInputProblem"},{"$ref":"#/components/schemas/QuoteRefusedProblem"}]}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Before checkout: re-quotes the hold with these extras and this pickup in place of its own, for the same start, group and currency, so checkout books what the traveller saw. Its places and its time left stay. Refused like a quote, the hold unchanged. `HoldExpired`: hold again. 409 once it is checked out.","summary":"Change a hold's extras and pickup","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldChoicesRequest"}}},"required":true}}},"/v1/public/checkout":{"post":{"tags":["public"],"operationId":"public.checkout","parameters":[],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"CheckedOutBooking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckedOutBooking"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/InvalidInputProblem"},{"$ref":"#/components/schemas/QuoteRefusedProblem"}]}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"A hold, by its `ref`, with the lead traveller: `confirmed` at once, or `pending` until the operator confirms. An on-request option checks out with its selection and no hold, and is `pending`. The price lines, policy, rate and trip are kept as sold. The answer carries `booking_token`, which opens the booking as its signed link does. Checking the same hold out again with the same lead changes nothing, and carries no token. `HoldExpired`: hold again. `HoldRequired`: this option is held first. `SellerUnavailable`: the site's seller is suspended and takes no new bookings for now.","summary":"Check out","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/CheckoutHold"},{"$ref":"#/components/schemas/CheckoutOnRequest"}]}}},"required":true}}},"/v1/public/bookings/{ref}":{"get":{"tags":["public"],"operationId":"public.getBooking","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"email","in":"query","schema":{"type":"string","description":"The lead traveller's email, any case: without it or the token, or with another, the booking is not found."},"required":false,"description":"The lead traveller's email, any case: without it or the token, or with another, the booking is not found."},{"name":"token","in":"query","schema":{"type":"string","description":"The token from the booking's signed link, in place of the email."},"required":false,"description":"The token from the booking's signed link, in place of the email."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"Booking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"By ref and the lead's email, the booking's token, or signed in as the traveller who owns it: its status, start, trip, payment and price lines; never the lead's contact details.","summary":"A booking"}},"/v1/public/bookings/{ref}/travellers":{"get":{"tags":["public"],"operationId":"public.getTravellerForm","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"email","in":"query","schema":{"type":"string","description":"The lead's email, any case."},"required":false,"description":"The lead's email, any case."},{"name":"token","in":"query","schema":{"type":"string","description":"The booking's signed token, or a traveller's own."},"required":false,"description":"The booking's signed token, or a traveller's own."},{"name":"traveller","in":"query","schema":{"type":"string","description":"With a traveller's own token: their position, from their link."},"required":false,"description":"With a traveller's own token: their position, from their link."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"TravellerForm","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TravellerForm"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"What the trip asks, how far along the booking is, and the travellers given so far, masked. The lead's email, the booking's token or a session as the traveller who owns it opens every card; a traveller's own token (with `traveller`) opens only theirs.","summary":"A booking's travellers' details, to fill in"}},"/v1/public/bookings/{ref}/travellers/{position}":{"put":{"tags":["public"],"operationId":"public.saveTravellerDetails","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"position","in":"path","schema":{"type":"string","description":"The traveller's place in the booking, 1 (the lead) to its `pax`.","title":"between(1, 100)"},"required":true,"description":"The traveller's place in the booking, 1 (the lead) to its `pax`."},{"name":"email","in":"query","schema":{"type":"string","description":"The lead's email, any case."},"required":false,"description":"The lead's email, any case."},{"name":"token","in":"query","schema":{"type":"string","description":"The booking's signed token, or a traveller's own."},"required":false,"description":"The booking's signed token, or a traveller's own."},{"name":"traveller","in":"query","schema":{"type":"string","description":"With a traveller's own token: their position, from their link."},"required":false,"description":"With a traveller's own token: their position, from their link."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"TravellerForm","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TravellerForm"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"One traveller, 1 being the lead, replacing what was there, except the fields a read masks (date of birth, passport number, medical note): left out or null, each keeps what is saved, and an empty string erases it. A masked value is refused. Checked as at checkout, with nothing required yet; only the asked fields are kept. Completing every traveller clears the booking's `overdue` flag.","summary":"Fill in a traveller's details","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TravellerSave"}}},"required":true}}},"/v1/public/bookings/{ref}/travellers/{position}/link":{"post":{"tags":["public"],"operationId":"public.travellerLink","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"position","in":"path","schema":{"type":"string","description":"The traveller's place in the booking, 1 (the lead) to its `pax`.","title":"between(1, 100)"},"required":true,"description":"The traveller's place in the booking, 1 (the lead) to its `pax`."},{"name":"email","in":"query","schema":{"type":"string","description":"The lead's email, any case."},"required":false,"description":"The lead's email, any case."},{"name":"token","in":"query","schema":{"type":"string","description":"The booking's signed token, or a traveller's own."},"required":false,"description":"The booking's signed token, or a traveller's own."},{"name":"traveller","in":"query","schema":{"type":"string","description":"With a traveller's own token: their position, from their link."},"required":false,"description":"With a traveller's own token: their position, from their link."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"TravellerLink","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TravellerLink"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"For the lead to forward, so each traveller fills in their own details and passports aren't passed around. Needs the lead's email, the booking's token, or a session as the traveller who owns it.","summary":"A link for one traveller"}},"/v1/public/bookings/{ref}/cancel":{"post":{"tags":["public"],"operationId":"public.cancelBooking","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"token","in":"query","schema":{"type":"string","description":"The token from the booking's signed link: required unless the traveller who owns the booking is signed in."},"required":false,"description":"The token from the booking's signed link: required unless the traveller who owns the booking is signed in."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"Booking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Through the booking's signed link, or signed in as the traveller who owns it (their session cookie, issue #324): an email alone never cancels. A confirmed booking is cancelled with the refund its policy gives now, as `/refund` previews; its places go back on sale and the traveller is emailed.","summary":"Cancel a booking, as its traveller","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"reason":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"Why, in the traveller's words, up to 500 characters; null to say nothing."}},"additionalProperties":false}}},"required":true}}},"/v1/public/bookings/{ref}/receipt":{"post":{"tags":["public"],"operationId":"public.uploadReceipt","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"email","in":"query","schema":{"type":"string","description":"The lead traveller's email, any case: without it or the token, or with another, the booking is not found."},"required":false,"description":"The lead traveller's email, any case: without it or the token, or with another, the booking is not found."},{"name":"token","in":"query","schema":{"type":"string","description":"The token from the booking's signed link, in place of the email."},"required":false,"description":"The token from the booking's signed link, in place of the email."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"Booking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"For a booking paid by bank transfer and not yet settled, by the lead's email, the booking's token, or signed in as the traveller who owns it. The body is the file itself: a PDF, or a JPEG, PNG or WebP image, up to 10 MB, told apart by its own bytes. A new one replaces the last. The payment becomes `proof_submitted`; when the storefront confirms transfers on the receipt, an instant booking is confirmed now. Staff read the file; it never gets a public URL.","summary":"Upload a transfer receipt","requestBody":{"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}},"required":true}}},"/v1/public/bookings/{ref}/refund":{"get":{"tags":["public"],"operationId":"public.refundPreview","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"email","in":"query","schema":{"type":"string","description":"The lead traveller's email, any case: without it or the token, or with another, the booking is not found."},"required":false,"description":"The lead traveller's email, any case: without it or the token, or with another, the booking is not found."},{"name":"token","in":"query","schema":{"type":"string","description":"The token from the booking's signed link, in place of the email."},"required":false,"description":"The token from the booking's signed link, in place of the email."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"RefundPreview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundPreview"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"For a confirmed booking, from its policy as sold.","summary":"What cancelling now gives back"}},"/v1/public/payment-methods":{"get":{"tags":["public"],"operationId":"public.paymentMethods","parameters":[{"name":"option_id","in":"query","schema":{"type":"string","description":"The option being booked, with `start`: only methods that fit before it are listed."},"required":false,"description":"The option being booked, with `start`: only methods that fit before it are listed."},{"name":"start","in":"query","schema":{"type":"string","description":"The start being booked, e.g. `2026-10-07T08:00`, or the date alone for an all-day start.","pattern":"^\\d{4}-\\d{2}-\\d{2}(T([01]\\d|2[0-3]):[0-5]\\d)?$"},"required":false,"description":"The start being booked, e.g. `2026-10-07T08:00`, or the date alone for an all-day start."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicPaymentMethod"}}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The storefront's enabled methods; with `option_id` and `start`, only those whose whole window to pay ends 48 hours before the start. On a marketplace whose sellers collect (ADR-0023), they are the option's seller's, from its own site, so pass `option_id`: without it the list is empty. Never the bank details: they come with checkout.","summary":"How a traveller can pay"}},"/v1/public/enquiries":{"post":{"tags":["public"],"operationId":"public.createEnquiry","parameters":[],"security":[{"publishableKey":[]}],"responses":{"202":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["received","id","reference"],"properties":{"received":{"type":"boolean","description":"Always true: the enquiry was accepted."},"id":{"anyOf":[{"$ref":"#/components/schemas/UUID"},{"type":"null"}],"description":"The enquiry's id; null when it wasn't kept."},"reference":{"anyOf":[{"type":"string","description":"a string matching the pattern ^EQ-[0-9A-F]{5}$","pattern":"^EQ-[0-9A-F]{5}$"},{"type":"null"}],"description":"What the traveller is shown, e.g. `EQ-3F2A1`: `EQ-` and the first five characters of the id, upper-cased."}},"additionalProperties":false}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"A question about a trip (`product`, with its `product_id`), a holiday-planner request (`planner`) or a message (`contact`), with who is asking and, when it helps, dates and travellers. On a marketplace a question about a trip goes to the trip's seller. The answer has its `id` and the `reference` to show the traveller (`EQ-` and the id's first five characters). Leave `website` empty: it is a trap for bots.","summary":"Send an enquiry","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnquiryInput"}}},"required":true}}},"/v1/public/offers/{id}":{"get":{"tags":["public"],"operationId":"public.getOffer","parameters":[{"name":"id","in":"path","schema":{"type":"string","description":"The offer's id, from its link."},"required":true,"description":"The offer's id, from its link."},{"name":"token","in":"query","schema":{"type":"string","description":"The token from the offer's signed link, as its email carries it."},"required":true,"description":"The token from the offer's signed link, as its email carries it."},{"name":"currency","in":"query","schema":{"type":"string","description":"A currency the storefront sells in, in place of its display currency.","pattern":"^[A-Z]{3}$"},"required":false,"description":"A currency the storefront sells in, in place of its display currency."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicOffer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicOffer"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The offer a seller sent on a price request through this storefront, opened by its signed link's `token` (issue #543): its trip, start, group, what it includes, its last day and, while it is open and bookable, its quote. 404 for any other storefront, or a wrong token.","summary":"A seller's price, by its link"}},"/v1/public/offers/{id}/hold":{"post":{"tags":["public"],"operationId":"public.holdOffer","parameters":[{"name":"id","in":"path","schema":{"type":"string","description":"The offer's id, from its link."},"required":true,"description":"The offer's id, from its link."},{"name":"token","in":"query","schema":{"type":"string","description":"The token from the offer's signed link, as its email carries it."},"required":true,"description":"The token from the offer's signed link, as its email carries it."}],"security":[{"publishableKey":[]}],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["ref","status","hold_expires_at","start","quote"],"properties":{"ref":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"status":{"type":"string","enum":["on_hold","pending","confirmed","completed","cancelled","rejected","expired"],"description":"Where the booking stands: `on_hold` while checkout holds its places, `pending` until the operator confirms it, `confirmed`, `completed` once the trip is over, or `cancelled`, `rejected` or `expired`."},"hold_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the places go back on sale."},"start":{"type":"string","description":"The start held, e.g. `2026-10-07T08:00`."},"quote":{"allOf":[{"$ref":"#/components/schemas/Quote"}],"description":"The price held, re-quoted as the hold was made: what checkout charges."}},"additionalProperties":false}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/InvalidInputProblem"},{"$ref":"#/components/schemas/QuoteRefusedProblem"}]}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Holds an open offer's start and group at its price for 15 minutes, until checkout, which books it as any hold (with `/v1/public/checkout`). Its extras and pickup can be changed on the hold. The price stands even where the trip confirms by hand or is booked on request: it is the seller's yes. 409 once it is expired, withdrawn or booked; refused like a quote when its start no longer can be.","summary":"Hold a seller's price","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferHoldRequest"}}},"required":true}}},"/v1/public/options/{optionId}/calendar":{"get":{"tags":["public"],"operationId":"public.calendar","parameters":[{"name":"optionId","in":"path","schema":{"type":"string","description":"The option's id, one of a published trip's."},"required":true,"description":"The option's id, one of a published trip's."},{"name":"month","in":"query","schema":{"type":"string","description":"The month, e.g. `2026-10`.","pattern":"^\\d{4}-(0[1-9]|1[0-2])$"},"required":true,"description":"The month, e.g. `2026-10`."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"Calendar","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Calendar"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Each day's status and start times, with places left, for a published trip's option.","summary":"An option's month"}},"/v1/public/redirect":{"get":{"tags":["public"],"operationId":"public.resolveRedirect","parameters":[{"name":"path","in":"query","schema":{"type":"string","description":"The path asked for, e.g. `/tours/nizwa.html`."},"required":true,"description":"The path asked for, e.g. `/tours/nizwa.html`."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PublicRedirect","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicRedirect"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"For a path that isn't found: where to send the visitor, and with which code. Counts the hit. 404 when it doesn't redirect.","summary":"Where an old path went"}},"/v1/public/places/search":{"get":{"tags":["publicPickups"],"operationId":"publicPickups.searchPickupPlaces","parameters":[{"name":"q","in":"query","schema":{"type":"string","description":"What the traveller typed so far, at least 2 characters, e.g. `musc`: matched against names in any language."},"required":true,"description":"What the traveller typed so far, at least 2 characters, e.g. `musc`: matched against names in any language."},{"name":"city","in":"query","schema":{"type":"string","description":"A city id or slug whose places come first, e.g. `muscat`."},"required":false,"description":"A city id or slug whose places come first, e.g. `muscat`."},{"name":"kind","in":"query","schema":{"type":"string","description":"Only these kinds, comma-separated, e.g. `airport,harbour`: the places library's kinds."},"required":false,"description":"Only these kinds, comma-separated, e.g. `airport,harbour`: the places library's kinds."},{"name":"limit","in":"query","schema":{"allOf":[{"$ref":"#/components/schemas/NumberFromString"}],"description":"At most this many, 1 to 10; 8 by default."},"required":false,"description":"At most this many, 1 to 10; 8 by default."},{"name":"locale","in":"query","schema":{"type":"string","description":"The language for names, e.g. `ar`: translated when the library has it, else English."},"required":false,"description":"The language for names, e.g. `ar`: translated when the library has it, else English."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PlaceSuggestionList","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceSuggestionList"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Places from the platform's places library whose name, in any language, contains `q`: best match first, at most `limit` (8 by default, 10 at most), only `kind` when given. Never an operator's own places.","summary":"Suggest pickup places as the traveller types"}},"/v1/public/options/{optionId}/pickup/resolve":{"get":{"tags":["publicPickups"],"operationId":"publicPickups.resolvePickup","parameters":[{"name":"optionId","in":"path","schema":{"type":"string","description":"The option's id, one of a published trip's."},"required":true,"description":"The option's id, one of a published trip's."},{"name":"q","in":"query","schema":{"type":"string","description":"What the traveller typed, e.g. `airport` or `Ruwi`: the best places library match, in any language. Send exactly one of `q`, `lat` + `lng`, or `place_id`."},"required":false,"description":"What the traveller typed, e.g. `airport` or `Ruwi`: the best places library match, in any language. Send exactly one of `q`, `lat` + `lng`, or `place_id`."},{"name":"lat","in":"query","schema":{"allOf":[{"$ref":"#/components/schemas/NumberFromString"}],"description":"Latitude of a map pin, e.g. `23.599`. Send with `lng`."},"required":false,"description":"Latitude of a map pin, e.g. `23.599`. Send with `lng`."},{"name":"lng","in":"query","schema":{"allOf":[{"$ref":"#/components/schemas/NumberFromString"}],"description":"Longitude of a map pin, e.g. `58.545`. Send with `lat`."},"required":false,"description":"Longitude of a map pin, e.g. `58.545`. Send with `lat`."},{"name":"place_id","in":"query","schema":{"type":"string","description":"A places library place from `GET /v1/public/places/search`."},"required":false,"description":"A places library place from `GET /v1/public/places/search`."},{"name":"start","in":"query","schema":{"type":"string","description":"The start being booked, e.g. `2026-11-13T08:00`: for `pickup_window`."},"required":false,"description":"The start being booked, e.g. `2026-11-13T08:00`: for `pickup_window`."},{"name":"pax","in":"query","schema":{"allOf":[{"$ref":"#/components/schemas/NumberFromString"}],"description":"Travellers, e.g. `2`: for `units` and `total`."},"required":false,"description":"Travellers, e.g. `2`: for `units` and `total`."},{"name":"locale","in":"query","schema":{"type":"string","description":"The language wanted, e.g. `fr`. Names and `explanation` are in English for now."},"required":false,"description":"The language wanted, e.g. `fr`. Names and `explanation` are in English for now."},{"name":"currency","in":"query","schema":{"type":"string","description":"One of the storefront's currencies, e.g. `OMR`; its display currency when left out."},"required":false,"description":"One of the storefront's currencies, e.g. `OMR`; its display currency when left out."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"PickupResolution","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PickupResolution"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/InvalidInputProblem"},{"$ref":"#/components/schemas/QuoteRefusedProblem"}]}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The one answer for a place on a published option: a pickup point of its plan wins, then the smallest area containing the place, then the plan's rule for everywhere else. `200` even when not offered. Send exactly one of `q` (the best places library match; `404` when nothing matches, so offer a pin), `lat` + `lng`, or `place_id`; else `422`. Prices are retail in `currency`, as a quote's `pickup` line; `CurrencyNotEnabled` for a currency the storefront doesn't sell in.","summary":"The pickup answer for a place"}},"/v1/public/bookings/{ref}/refund-instead":{"get":{"tags":["publicDepartures"],"operationId":"publicDepartures.refundInsteadOffer","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"token","in":"query","schema":{"type":"string","description":"The token from the booking's signed link: required unless the traveller who owns the booking is signed in."},"required":false,"description":"The token from the booking's signed link: required unless the traveller who owns the booking is signed in."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"RefundInsteadOffer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundInsteadOffer"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"For a booking moved off a departure called off: why, the start it was moved from and to, and everything paid, which the refund gives back. `404` when nothing is offered: never moved, the refund already taken, or changed again since; `409` once the new start has passed.","summary":"A moved booking's full refund instead"},"post":{"tags":["publicDepartures"],"operationId":"publicDepartures.refundInstead","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"token","in":"query","schema":{"type":"string","description":"The token from the booking's signed link: required unless the traveller who owns the booking is signed in."},"required":false,"description":"The token from the booking's signed link: required unless the traveller who owns the booking is signed in."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"Booking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Through the booking's signed link, or signed in as the traveller who owns it, until the new start: the booking is cancelled by the operator (a pending one declined) with everything paid refunded, its places go back on sale, and the traveller is emailed. Once.","summary":"Take the full refund instead of the move"}},"/v1/public/auth/sign-in-link":{"post":{"tags":["travellerSignIn"],"operationId":"travellerSignIn.requestSignInLink","parameters":[],"security":[{"publishableKey":[]}],"responses":{"202":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["sent"],"properties":{"sent":{"type":"boolean","description":"Always true, whether or not the email is known."}},"additionalProperties":false}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Sends the traveller a link that signs them in, from the storefront, in their language. It opens `callback_url` with `token` added; that page sends the token to `/v1/public/auth/verify`. The link works once, for 15 minutes by default. `callback_url` must be on the storefront's own site (the site of its `manage_booking_url`), else `422`; a storefront with no site yet is `409`. Past the hourly limits, by default 5 links for one email and 20 from one address (the first `X-Forwarded-For` address, which a storefront's server passes on), `429` with `retry_after_seconds`.","summary":"Email a sign-in link","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignInLinkRequest"}}},"required":true}}},"/v1/public/auth/verify":{"post":{"tags":["travellerSignIn"],"operationId":"travellerSignIn.verifySignInLink","parameters":[],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"SignedIn","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedIn"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Signs the traveller in, setting their session cookie, and makes the guest bookings made with their email theirs, each once. A token works once and only until it expires; a used, expired or unknown one is `401`: ask for a new link. An email seen for the first time makes an account.","summary":"Sign in with the link's token","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"The `token` the link added to the page.","title":"maxLength(200)","minLength":1,"maxLength":200}},"additionalProperties":false}}},"required":true}}},"/v1/public/auth/sign-out":{"post":{"tags":["travellerSignIn"],"operationId":"travellerSignIn.signOut","parameters":[],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["signed_out"],"properties":{"signed_out":{"type":"boolean","description":"Always true: no session is left, whether or not there was one."}},"additionalProperties":false}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Ends the traveller's session, if there is one, and clears its cookie.","summary":"Sign out"}},"/v1/public/me":{"get":{"tags":["travellerMe"],"operationId":"travellerMe.getTraveller","parameters":[],"security":[{"publishableKey":[]},{"travellerSession":[]}],"responses":{"200":{"description":"SignedInTraveller","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedInTraveller"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The signed-in traveller; `401` when nobody is.","summary":"Who is signed in"}},"/v1/public/me/bookings":{"get":{"tags":["travellerMe"],"operationId":"travellerMe.listMyBookings","parameters":[],"security":[{"publishableKey":[]},{"travellerSession":[]}],"responses":{"200":{"description":"MyBookings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyBookings"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The signed-in traveller's bookings across every storefront, newest first, each as `/v1/public/bookings/{ref}` shows it, with the storefront that sold it. Guest bookings made with their email since they signed in become theirs first. Through the storefront that sold one, the traveller opens it, sees its refund and cancels it with this session in place of the token.","summary":"My bookings"}},"/v1/public/me/price-requests":{"get":{"tags":["travellerMe"],"operationId":"travellerMe.listMyPriceRequests","parameters":[],"security":[{"publishableKey":[]},{"travellerSession":[]}],"responses":{"200":{"description":"MyPriceRequests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyPriceRequests"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The price requests the signed-in traveller sent through this storefront, newest first, so they follow them to another device: each a question about a trip with who would travel (`POST /v1/public/enquiries` with `kind: product` and `travellers`), sent with the email they proved, signed in or not. Each says where it stands and, once the seller sent a price, how to open it. Never the message or phone sent with it. At most the newest 50.","summary":"My price requests"}},"/v1/public/me/saved-trips":{"get":{"tags":["travellerMe"],"operationId":"travellerMe.listSavedTrips","parameters":[],"security":[{"publishableKey":[]},{"travellerSession":[]}],"responses":{"200":{"description":"SavedTrips","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedTrips"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The trips the signed-in traveller saved on this storefront, newest first, so they follow them to another device. Each is a trip still on sale here; one no longer sold is left out.","summary":"My saved trips"},"post":{"tags":["travellerMe"],"operationId":"travellerMe.saveTrips","parameters":[],"security":[{"publishableKey":[]},{"travellerSession":[]}],"responses":{"200":{"description":"SavedTrips","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedTrips"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Saves one trip, or the list a guest saved in their browser once they sign in (newest first). A trip already saved keeps its place; one not on sale on this storefront is skipped. The list keeps the newest 200. Answers with the list.","summary":"Save trips","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveTripsRequest"}}},"required":true}}},"/v1/public/me/saved-trips/{productId}":{"delete":{"tags":["travellerMe"],"operationId":"travellerMe.unsaveTrip","parameters":[{"name":"productId","in":"path","schema":{"type":"string","description":"The trip's id.","pattern":"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"},"required":true,"description":"The trip's id."}],"security":[{"publishableKey":[]},{"travellerSession":[]}],"responses":{"200":{"description":"SavedTrips","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedTrips"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Answers with the list. A trip that wasn't saved changes nothing.","summary":"Take a trip out of my saved trips"}},"/v1/public/auth/passkeys/sign-in/options":{"post":{"tags":["travellerPasskeySignIn"],"operationId":"travellerPasskeySignIn.passkeySignInOptions","parameters":[],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"WebAuthn's `PublicKeyCredentialRequestOptionsJSON`, for `PublicKeyCredential.parseRequestOptionsFromJSON` and `navigator.credentials.get()` (or `@simplewebauthn/browser`'s `startAuthentication`). No `allowCredentials`: the browser offers the traveller's passkeys for this site. Its `challenge` works once, for 5 minutes by default.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasskeyRequestOptions"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"A challenge for the browser to sign with one of the traveller's passkeys for this storefront's site (the origin of its `manage_booking_url`); a storefront with no site yet is `409`. Past the hourly limit, by default 20 from one address (the first `X-Forwarded-For` address), `429` with `retry_after_seconds`.","summary":"Start signing in with a passkey"}},"/v1/public/auth/passkeys/sign-in":{"post":{"tags":["travellerPasskeySignIn"],"operationId":"travellerPasskeySignIn.passkeySignIn","parameters":[],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"SignedIn","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedIn"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Signs the traveller in, setting their session cookie as `/v1/public/auth/verify` does, and makes the guest bookings made with their email theirs, each once. The passkey must be one added on this storefront's site and sign a challenge given for it, unspent and in time, from the site's own origin; its signature counter must move on. Anything else is `401`, and spends the challenge.","summary":"Sign in with a passkey","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasskeyAssertion"}}},"required":true}}},"/v1/public/auth/passkeys/register/options":{"post":{"tags":["travellerPasskeys"],"operationId":"travellerPasskeys.passkeyRegistrationOptions","parameters":[],"security":[{"publishableKey":[]},{"travellerSession":[]}],"responses":{"200":{"description":"WebAuthn's `PublicKeyCredentialCreationOptionsJSON`, for `PublicKeyCredential.parseCreationOptionsFromJSON` and `navigator.credentials.create()` (or `@simplewebauthn/browser`'s `startRegistration`). Its `challenge` works once, for 5 minutes by default.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasskeyCreationOptions"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Creation options for a passkey on this storefront's site (the origin of its `manage_booking_url`), for the signed-in traveller; a storefront with no site yet is `409`. The passkeys they already have there are excluded.","summary":"Start adding a passkey"}},"/v1/public/auth/passkeys/register":{"post":{"tags":["travellerPasskeys"],"operationId":"travellerPasskeys.addPasskey","parameters":[],"security":[{"publishableKey":[]},{"travellerSession":[]}],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["id","name","rp_id","synced","created_at","last_used_at"],"properties":{"id":{"type":"string","description":"The passkey's id."},"name":{"type":"string","description":"What the traveller called it."},"rp_id":{"type":"string","description":"The site it signs in on: the host of that storefront's `manage_booking_url`."},"synced":{"type":"boolean","description":"Whether the authenticator backs it up, e.g. to the traveller's cloud keychain."},"created_at":{"type":"string","description":"When it was added, as an ISO 8601 timestamp in UTC."},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When it last signed in; null until it has."}},"additionalProperties":false}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"Adds the passkey the browser made from the options. It must answer a challenge given to this traveller for this site, unspent and in time, from the site's own origin, else `422`; each challenge works once. A passkey already added is `409`. It then signs in on this storefront's site only.","summary":"Add a passkey","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasskeyRegistration"}}},"required":true}}},"/v1/public/me/passkeys":{"get":{"tags":["travellerPasskeys"],"operationId":"travellerPasskeys.listMyPasskeys","parameters":[],"security":[{"publishableKey":[]},{"travellerSession":[]}],"responses":{"200":{"description":"MyPasskeys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyPasskeys"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The signed-in traveller's passkeys, on every storefront's site, each with the site it signs in on.","summary":"My passkeys"}},"/v1/public/me/passkeys/{id}":{"delete":{"tags":["travellerPasskeys"],"operationId":"travellerPasskeys.removeMyPasskey","parameters":[{"name":"id","in":"path","schema":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The passkey's id."},"required":true,"description":"The passkey's id."}],"security":[{"publishableKey":[]},{"travellerSession":[]}],"responses":{"204":{"description":"Success"},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"It no longer signs in. Only the traveller's own: another's is `404`.","summary":"Remove a passkey"}},"/v1/public/bookings/{ref}/contact-changes":{"post":{"tags":["contactChanges"],"operationId":"contactChanges.requestContactChange","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"token","in":"query","schema":{"type":"string","description":"The token from the booking's signed link: required unless the traveller who owns the booking is signed in."},"required":false,"description":"The token from the booking's signed link: required unless the traveller who owns the booking is signed in."}],"security":[{"publishableKey":[]}],"responses":{"202":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["id","field","status","value","code_sent_to","expires_at","verified_at"],"properties":{"id":{"type":"string","description":"The change's id, to send its code back to."},"field":{"type":"string","enum":["email","phone"],"description":"Which contact it changes: `email` or `phone`."},"status":{"type":"string","enum":["pending","verified","cancelled"],"description":"`pending` until the code comes back; `verified` once it has taken effect."},"value":{"type":"string","description":"The new contact, masked, e.g. `l••••h@gm••l.com` or `+49 ••• ••• 789`."},"code_sent_to":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where the code went, masked: the new email, or the booking's email for a new phone."},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the code stops working."},"verified_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When it took effect; null until it has."}},"additionalProperties":false}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"One contact per request. A six-digit code is emailed: for a new `email`, to that address, and the old one stays the booking's until the code comes back; for a new `phone`, to the booking's email, since a change is proven through the other channel. The traveller fixes the email themselves only within 30 minutes of booking (by default); after that it needs a check through the phone, not offered yet, so `409`: the operator corrects it. A change of the other contact still waiting for its code is `409`. By default 5 requests an hour per booking, then `429`. The code works for 15 minutes by default; asking again replaces it.","summary":"Ask to fix the booking's email or phone","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["field","value"],"properties":{"field":{"type":"string","enum":["email"],"description":"`email`: the booking's email."},"value":{"type":"string","description":"The new email. A code is sent to it; the old one stays the booking's until the code comes back."},"locale":{"type":"string","description":"The language for the code's email, e.g. `de`; the storefront's default when absent or not one of its languages.","pattern":"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$","title":"maxLength(35)","maxLength":35}},"additionalProperties":false},{"type":"object","required":["field","value"],"properties":{"field":{"type":"string","enum":["phone"],"description":"`phone`: the booking's phone."},"value":{"type":"string","description":"The new mobile or WhatsApp number, with its country code. A code is sent to the booking's email to prove it.","pattern":"^\\+[1-9]\\d{6,14}$"},"locale":{"type":"string","description":"The language for the code's email, e.g. `de`; the storefront's default when absent or not one of its languages.","pattern":"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$","title":"maxLength(35)","maxLength":35}},"additionalProperties":false}]}}},"required":true}}},"/v1/public/bookings/{ref}/contact-changes/{id}/confirm":{"post":{"tags":["contactChanges"],"operationId":"contactChanges.confirmContactChange","parameters":[{"name":"ref","in":"path","schema":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"required":true,"description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},{"name":"id","in":"path","schema":{"type":"string","description":"The change's id."},"required":true,"description":"The change's id."},{"name":"token","in":"query","schema":{"type":"string","description":"The token from the booking's signed link: required unless the traveller who owns the booking is signed in."},"required":false,"description":"The token from the booking's signed link: required unless the traveller who owns the booking is signed in."}],"security":[{"publishableKey":[]}],"responses":{"200":{"description":"ContactChange","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactChange"}}}},"401":{"description":"UnauthenticatedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/UnauthenticatedProblem"}}}},"403":{"description":"OriginNotAllowedProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/OriginNotAllowedProblem"}}}},"404":{"description":"NotFoundProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/NotFoundProblem"}}}},"409":{"description":"ConflictProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ConflictProblem"}}}},"422":{"description":"InvalidInputProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/InvalidInputProblem"}}}},"429":{"description":"TooManyRequestsProblem","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/TooManyRequestsProblem"}}}}},"description":"The change takes effect, once: the booking's details go to a new email, and the old address is told; a new phone is told at the email. A wrong code is `422` with the tries left; after 5 wrong codes (by default) the change is cancelled, and an expired or cancelled one is `409`: ask again. Sending a used code again changes nothing.","summary":"Send the code back","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactChangeCode"}}},"required":true}}}},"components":{"schemas":{"InvalidInputProblem":{"type":"object","required":["type","title","status","code","detail"],"properties":{"type":{"type":"string","enum":["about:blank"],"description":"Always `about:blank`: `code` says what the problem is."},"title":{"type":"string","enum":["Unprocessable Content"],"description":"The HTTP status phrase."},"status":{"type":"number","enum":[422],"description":"The HTTP status code."},"code":{"type":"string","enum":["InvalidInput"],"description":"What went wrong, by name: the field to branch on."},"detail":{"type":"string","description":"What went wrong, in a sentence of English for developers."}},"additionalProperties":false},"UnauthenticatedProblem":{"type":"object","required":["type","title","status","code","detail"],"properties":{"type":{"type":"string","enum":["about:blank"],"description":"Always `about:blank`: `code` says what the problem is."},"title":{"type":"string","enum":["Unauthorized"],"description":"The HTTP status phrase."},"status":{"type":"number","enum":[401],"description":"The HTTP status code."},"code":{"type":"string","enum":["Unauthenticated"],"description":"What went wrong, by name: the field to branch on."},"detail":{"type":"string","description":"What went wrong, in a sentence of English for developers."}},"additionalProperties":false},"TooManyRequestsProblem":{"type":"object","required":["type","title","status","code","detail","retry_after_seconds"],"properties":{"type":{"type":"string","enum":["about:blank"],"description":"Always `about:blank`: `code` says what the problem is."},"title":{"type":"string","enum":["Too Many Requests"],"description":"The HTTP status phrase."},"status":{"type":"number","enum":[429],"description":"The HTTP status code."},"code":{"type":"string","enum":["RateLimited"],"description":"What went wrong, by name: the field to branch on."},"detail":{"type":"string","description":"What went wrong, in a sentence of English for developers."},"retry_after_seconds":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many seconds to wait before trying again."}},"additionalProperties":false},"Int":{"type":"integer","description":"an integer","title":"int"},"NotFoundProblem":{"type":"object","required":["type","title","status","code","detail","entity"],"properties":{"type":{"type":"string","enum":["about:blank"],"description":"Always `about:blank`: `code` says what the problem is."},"title":{"type":"string","enum":["Not Found"],"description":"The HTTP status phrase."},"status":{"type":"number","enum":[404],"description":"The HTTP status code."},"code":{"type":"string","enum":["NotFound"],"description":"What went wrong, by name: the field to branch on."},"detail":{"type":"string","description":"What went wrong, in a sentence of English for developers."},"entity":{"type":"string","description":"What wasn't found, e.g. `product` or `booking`."}},"additionalProperties":false},"PublicStorefront":{"type":"object","required":["id","operator_id","kind","name","default_locale","locales","display_currency","currencies","display_rounding","blog_url"],"properties":{"id":{"type":"string","description":"The storefront's id.","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"operator_id":{"type":"string","description":"The operator it belongs to; for a marketplace, the platform.","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"kind":{"type":"string","enum":["direct","marketplace"],"description":"`direct`: one operator's own site. `marketplace`: Tour Pingo, selling every operator's trips approved for the network."},"name":{"type":"string","description":"Its name."},"default_locale":{"type":"string","description":"The language pages are in unless the traveller picks another."},"locales":{"type":"array","items":{"type":"string"},"description":"The languages it publishes."},"display_currency":{"type":"string","description":"The currency prices are shown in by default."},"currencies":{"type":"array","items":{"type":"string"},"description":"The currencies a traveller can pick."},"display_rounding":{"type":"string","enum":["none","up_to_whole","up_to_99"],"description":"How converted prices are rounded for display: `none`, `up_to_whole` or `up_to_99`."},"blog_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The blog to link to, when the storefront shows one; else null."}},"additionalProperties":false},"OriginNotAllowedProblem":{"type":"object","required":["type","title","status","code","detail","origin"],"properties":{"type":{"type":"string","enum":["about:blank"],"description":"Always `about:blank`: `code` says what the problem is."},"title":{"type":"string","enum":["Forbidden"],"description":"The HTTP status phrase."},"status":{"type":"number","enum":[403],"description":"The HTTP status code."},"code":{"type":"string","enum":["OriginNotAllowed"],"description":"What went wrong, by name: the field to branch on."},"detail":{"type":"string","description":"What went wrong, in a sentence of English for developers."},"origin":{"type":"string","description":"The page's origin that was refused, e.g. `https://www.example.com`."}},"additionalProperties":false},"PublicPage":{"type":"object","required":["id","slug","locale","title","sections","seo","updated_at"],"properties":{"id":{"type":"string","description":"The page's id.","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"slug":{"type":"string","description":"Its address, e.g. `about`."},"locale":{"type":"string","description":"The language served: the one asked for when the page has it, else the storefront's default."},"title":{"type":"string","description":"The page's title in `locale`."},"sections":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/HeroSection"},{"$ref":"#/components/schemas/TextSection"},{"$ref":"#/components/schemas/PublicTripsSection"},{"$ref":"#/components/schemas/PhotoSection"},{"$ref":"#/components/schemas/FaqSection"},{"$ref":"#/components/schemas/CtaSection"}]},"description":"The page's blocks, in order."},"seo":{"allOf":[{"$ref":"#/components/schemas/Seo"}],"description":"What to tell search engines about it."},"updated_at":{"type":"string","description":"When this language last changed."}},"additionalProperties":false},"HeroSection":{"type":"object","required":["type","title"],"properties":{"type":{"type":"string","enum":["hero"],"description":"Always `hero`: which kind of block this is."},"title":{"type":"string","description":"The big title, up to 120 characters."},"subtitle":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"A line under it, up to 240 characters; null for none."},"image":{"anyOf":[{"type":"object","required":["url","alt"],"properties":{"url":{"type":"string","description":"The photo's address: a path on the same site, or `https://`."},"alt":{"type":"string","description":"What it shows, for people who can't see it, up to 250 characters."}},"additionalProperties":false},{"type":"null"}],"description":"The photo behind the title; null for none."}},"additionalProperties":false,"description":"A large title over a photo, at the top of a page."},"TextSection":{"type":"object","required":["type","body"],"properties":{"type":{"type":"string","enum":["text"],"description":"Always `text`: which kind of block this is."},"body":{"type":"string","description":"Markdown, up to 20,000 characters."}},"additionalProperties":false},"PublicTripsSection":{"type":"object","required":["type","heading","trips"],"properties":{"type":{"type":"string","enum":["trips"],"description":"Always `trips`: which kind of block this is."},"heading":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A heading above the trips; null for none."},"trips":{"type":"array","items":{"$ref":"#/components/schemas/PublicPageTrip"},"description":"The published ones, in order, titled in the page's language when published in it."}},"additionalProperties":false},"PublicPageTrip":{"type":"object","required":["id","slug","title"],"properties":{"id":{"type":"string","description":"The trip's id.","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"slug":{"type":"string","description":"Its slug, which opens its page."},"title":{"type":"string","description":"Its title, in the page's language when published in it, else English."}},"additionalProperties":false},"PhotoSection":{"type":"object","required":["type","url","alt"],"properties":{"type":{"type":"string","enum":["photo"],"description":"Always `photo`: which kind of block this is."},"url":{"type":"string","description":"The photo's address: a path on the same site, or `https://`."},"alt":{"type":"string","description":"What it shows, for people who can't see it, up to 250 characters."},"caption":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"A line under it, up to 250 characters; null for none."}},"additionalProperties":false},"FaqSection":{"type":"object","required":["type","items"],"properties":{"type":{"type":"string","enum":["faq"],"description":"Always `faq`: which kind of block this is."},"heading":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"A heading above the questions, up to 120 characters; null for none."},"items":{"type":"array","items":{"type":"object","required":["question","answer"],"properties":{"question":{"type":"string","description":"The question, up to 300 characters."},"answer":{"type":"string","description":"Its answer, in Markdown, up to 5,000 characters."}},"additionalProperties":false},"description":"The questions and answers, 1 to 50, in order.","title":"maxItems(50)","minItems":1,"maxItems":50}},"additionalProperties":false},"CtaSection":{"type":"object","required":["type","label","href"],"properties":{"type":{"type":"string","enum":["cta"],"description":"Always `cta`: which kind of block this is."},"label":{"type":"string","description":"The button's words, up to 60 characters."},"href":{"type":"string","description":"Where it goes: a path on the same site, or a `https://`, `mailto:` or `tel:` link."}},"additionalProperties":false,"description":"A button to another page or site."},"Seo":{"type":"object","required":[],"properties":{"meta_title":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"The title search engines show; up to 60 characters shows in full."},"meta_description":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"The snippet search engines show; up to 160 characters shows in full."},"canonical_url":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"The page's main address, when the same content lives at another."},"noindex":{"type":"boolean","description":"Ask search engines not to list it."}},"additionalProperties":false},"PublicPageList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicPageEntry"},"description":"The published pages search engines may list."}},"additionalProperties":false},"PublicPageEntry":{"type":"object","required":["slug","updated_at"],"properties":{"slug":{"type":"string","description":"The page's address, e.g. `about`: the site shows it at `/{lang}/{slug}`."},"updated_at":{"type":"string","description":"When it last changed."}},"additionalProperties":false},"PublicMenu":{"type":"object","required":["location","items"],"properties":{"location":{"type":"string","enum":["header","footer"],"description":"Which menu: the `header` or the `footer`."},"items":{"type":"array","items":{"type":"object","required":["label","href","children"],"properties":{"label":{"type":"string","description":"Its words, in the language asked for, else the storefront's default."},"href":{"type":"string","description":"Where it goes: a path on the site, or a full address."},"children":{"type":"array","items":{"type":"object","required":["label","href"],"properties":{"label":{"type":"string","description":"Its words, in the language asked for, else the storefront's default."},"href":{"type":"string","description":"Where it goes: a path on the site, or a full address."}},"additionalProperties":false},"description":"The items under it, in order."}},"additionalProperties":false},"description":"The menu's items, in order."}},"additionalProperties":false},"TripList":{"type":"object","required":["data","next_cursor","total"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","slug","template","content_locale","title","summary","duration_minutes","duration_days","duration_nights","sales_paused","cover","city","region","country","categories","visits","pickup_time","hotel_pickup","private","vehicle","rating","place_label","start"],"properties":{"id":{"type":"string","description":"The trip's id, which opens its page."},"slug":{"type":"string","description":"Its slug, which opens its page in place of the id; on a marketplace, with its `seller`'s slug."},"template":{"type":"string","description":"What kind of trip it is: `day_tour`, `multi_day`, `transfer`, `ticket` or `activity`."},"content_locale":{"type":"string","description":"The language the text is in: `locale` when the trip is published in it, else `en`."},"title":{"type":"string","description":"Its title, in `content_locale`."},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A short paragraph about it; null for none."},"duration_minutes":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"How long it lasts, in minutes, for any trip but a `multi_day` one; null for those, or when unsaid."},"duration_days":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"How many days a `multi_day` trip lasts; null for other trips, or when unsaid."},"duration_nights":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"How many nights a `multi_day` trip lasts, fewer than its days; null for other trips, or when unsaid."},"sales_paused":{"type":"boolean","description":"Shown, but not bookable for now."},"cover":{"anyOf":[{"type":"object","required":["url","alt"],"properties":{"url":{"type":"string","description":"Where the photo is served."},"alt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What it shows, for screen readers; null when unsaid."}},"additionalProperties":false},{"type":"null"}],"description":"Its first photo; null when it has none."},"city":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The city it starts in; null when it has no start place or the library doesn't know the place's city."},"region":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"That city's region; null with the city."},"country":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The country it starts in; null when it has no start place."},"categories":{"type":"array","items":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},"description":"The platform categories it is in, main one first, each with its slug (as the `category` filter takes it) and its name in `locale`, else English."},"seller":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"The operator's slug, e.g. `vpo`: on a marketplace, the `seller` that opens a trip's page by its slug."},"name":{"type":"string","description":"The operator's name, shown on the trip."}},"additionalProperties":false,"description":"The operator that sells the trip: on a direct storefront, the storefront's own operator; on a marketplace, the seller whose network listing it is."},"from_price":{"anyOf":[{"$ref":"#/components/schemas/FromPrice"},{"type":"null"}],"description":"The lowest price per person, as on the trip's page; on every card of the trip list and the search."},"visits":{"type":"array","items":{"type":"object","required":["slug","name","lat","lng"],"properties":{"slug":{"type":"string","description":"The place's slug, e.g. `wadi-shab`."},"name":{"type":"string","description":"Its name in `locale` when translated, else English."},"lat":{"type":"number","description":"Latitude, in decimal degrees."},"lng":{"type":"number","description":"Longitude, in decimal degrees."}},"additionalProperties":false},"description":"The places its stops visit, in the day plan's order, each once, named in `locale` when translated, else English: not the pickup, the drop-off or a place passed by. As `via` takes them."},"pickup_time":{"anyOf":[{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","description":"The pickup time, or when its window opens, e.g. `08:00`."},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the window closes, e.g. `08:30`; null for a set time."}},"additionalProperties":false},{"type":"null"}],"description":"The first day's pickup time, e.g. `08:00`, or its window (`to`); null when the plan doesn't start with a timed pickup."},"hotel_pickup":{"type":"boolean","description":"Whether an option on sale picks travellers up where they stay: its pickup plan's mode is `pickup` or `both`. Where it starts is `city`."},"private":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Whether its main option (the default on sale, else the first on sale) is private; null with none on sale."},"vehicle":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The main option's vehicle as the seller writes it, e.g. `Private 4WD`, else as it named its vehicle unit, e.g. `Land Cruiser`; null without either."},"rating":{"anyOf":[{"$ref":"#/components/schemas/PublicSourceRating"},{"type":"null"}],"description":"The trip's rating on the seller's own site, as on its page's `source_rating`; null for none. For the card's chip."},"place_label":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where it goes in a few words, for the card's eyebrow after the main category, e.g. `Coast road south of Muscat`; null when unsaid."},"start":{"anyOf":[{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number","description":"Latitude, in decimal degrees."},"lng":{"type":"number","description":"Longitude, in decimal degrees."}},"additionalProperties":false},{"type":"null"}],"description":"Its start place's point; null without a start place."},"group_price":{"anyOf":[{"$ref":"#/components/schemas/GroupPrice"},{"type":"null"}],"description":"With `travellers`: what that many pay; null when it can't be booked for them or has no published price."},"next_group_price":{"anyOf":[{"$ref":"#/components/schemas/GroupPrice"},{"type":"null"}],"description":"With `travellers`: what one more would pay, for \"each with 3\"; null as `group_price` is."}},"additionalProperties":false},"description":"This page's trips: the best matches for `q` first, else the newest, unless `sort` asks for another order."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass it as `cursor` for the next page; null on the last."},"total":{"type":"integer","description":"How many trips match the filters, whatever the page.","title":"int"},"facets":{"type":"object","required":["price","category","duration","seller","via","city","each"],"properties":{"price":{"type":"array","items":{"type":"object","required":["value","name","count"],"properties":{"value":{"type":"string","description":"What the filter takes: a slug or a code."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its name in `locale`, else English; null for a code."},"count":{"type":"integer","description":"How many trips it would show, with every other filter kept; 0 when none.","title":"int"}},"additionalProperties":false},"description":"`shown` and `ask`, as `price` takes them."},"category":{"type":"array","items":{"type":"object","required":["value","name","count"],"properties":{"value":{"type":"string","description":"What the filter takes: a slug or a code."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its name in `locale`, else English; null for a code."},"count":{"type":"integer","description":"How many trips it would show, with every other filter kept; 0 when none.","title":"int"}},"additionalProperties":false},"description":"The categories, as `category` takes them, most trips first."},"duration":{"type":"array","items":{"type":"object","required":["value","name","count"],"properties":{"value":{"type":"string","description":"What the filter takes: a slug or a code."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its name in `locale`, else English; null for a code."},"count":{"type":"integer","description":"How many trips it would show, with every other filter kept; 0 when none.","title":"int"}},"additionalProperties":false},"description":"`up_to_3h`, `3h_to_6h`, `full_day` and `multi_day`, as `duration` takes them; only those some trip has."},"seller":{"type":"array","items":{"type":"object","required":["value","name","count"],"properties":{"value":{"type":"string","description":"What the filter takes: a slug or a code."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its name in `locale`, else English; null for a code."},"count":{"type":"integer","description":"How many trips it would show, with every other filter kept; 0 when none.","title":"int"}},"additionalProperties":false},"description":"The sellers, as `seller` takes them, most trips first."},"via":{"type":"array","items":{"type":"object","required":["value","name","count"],"properties":{"value":{"type":"string","description":"What the filter takes: a slug or a code."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its name in `locale`, else English; null for a code."},"count":{"type":"integer","description":"How many trips it would show, with every other filter kept; 0 when none.","title":"int"}},"additionalProperties":false},"description":"The places the trips' stops visit, as `via` takes them, most trips first."},"city":{"type":"array","items":{"type":"object","required":["value","name","count"],"properties":{"value":{"type":"string","description":"What the filter takes: a slug or a code."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its name in `locale`, else English; null for a code."},"count":{"type":"integer","description":"How many trips it would show, with every other filter kept; 0 when none.","title":"int"}},"additionalProperties":false},"description":"The cities the trips start in, as `city` takes them, most trips first: with `q`, where the trips with those words are."},"each":{"anyOf":[{"type":"object","required":["min","max"],"properties":{"min":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The lowest, in minor units of the list's currency."},"max":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The highest, in minor units of the list's currency."}},"additionalProperties":false},{"type":"null"}],"description":"The lowest and highest one traveller pays, as `max_price` reads it, among the trips the other filters keep; null when none has a price."}},"additionalProperties":false,"description":"With `facets=true`: the counts beside each filter, over the first 1000 trips the other parameters find (the best matches for `q`, else the newest)."}},"additionalProperties":false},"FromPrice":{"type":"object","required":["amount","currency","group_size"],"properties":{"amount":{"type":"integer","description":"Per person, in minor units of `currency`, rounded up the storefront's way, e.g. 43000 for OMR 43.000.","title":"int"},"currency":{"type":"string","description":"The `currency` asked for when it is one of the storefront's, else its display currency."},"group_size":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many travellers that price is for: on a private tour the price per person drops as the group grows."}},"additionalProperties":false},"PublicSourceRating":{"type":"object","required":["score","count","source"],"properties":{"score":{"type":"number","description":"Out of 5, with one decimal, e.g. `4.5`."},"count":{"type":"integer","description":"How many reviews it is from, e.g. `848`.","title":"int"},"source":{"type":"string","description":"Where they were left, e.g. `Vacation Packages Oman`: the seller's own site, not Tour Pingo."}},"additionalProperties":false},"GroupPrice":{"type":"object","required":["travellers","total","each"],"properties":{"travellers":{"type":"integer","description":"How many travellers it is for: `travellers`, or one more.","title":"int"},"total":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What the group pays in all, in the list's currency, on the first day each price applies from today."},"each":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"The total shared out, rounded up, then the storefront's way."}},"additionalProperties":false},"Money":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","description":"Minor units of the currency, e.g. 12500 for USD 125.00 or 580595 for OMR 580.595.","title":"int"},"currency":{"type":"string","description":"The ISO 4217 currency code, e.g. `USD`, `EUR` or `OMR`."}},"additionalProperties":false,"description":"An amount of money: integer minor units of an ISO 4217 currency, never a float."},"TripSearch":{"type":"object","required":["data","total","page","facets"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","slug","template","content_locale","title","summary","duration_minutes","duration_days","duration_nights","sales_paused","cover","city","region","country","categories"],"properties":{"id":{"type":"string","description":"The trip's id, which opens its page."},"slug":{"type":"string","description":"Its slug, which opens its page in place of the id; on a marketplace, with its `seller`'s slug."},"template":{"type":"string","description":"What kind of trip it is: `day_tour`, `multi_day`, `transfer`, `ticket` or `activity`."},"content_locale":{"type":"string","description":"The language the text is in: `locale` when the trip is published in it, else `en`."},"title":{"type":"string","description":"Its title, in `content_locale`."},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A short paragraph about it; null for none."},"duration_minutes":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"How long it lasts, in minutes, for any trip but a `multi_day` one; null for those, or when unsaid."},"duration_days":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"How many days a `multi_day` trip lasts; null for other trips, or when unsaid."},"duration_nights":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"How many nights a `multi_day` trip lasts, fewer than its days; null for other trips, or when unsaid."},"sales_paused":{"type":"boolean","description":"Shown, but not bookable for now."},"cover":{"anyOf":[{"type":"object","required":["url","alt"],"properties":{"url":{"type":"string","description":"Where the photo is served."},"alt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What it shows, for screen readers; null when unsaid."}},"additionalProperties":false},{"type":"null"}],"description":"Its first photo; null when it has none."},"city":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The city it starts in; null when it has no start place or the library doesn't know the place's city."},"region":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"That city's region; null with the city."},"country":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The country it starts in; null when it has no start place."},"categories":{"type":"array","items":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},"description":"The platform categories it is in, main one first, each with its slug (as the `category` filter takes it) and its name in `locale`, else English."},"seller":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"The operator's slug, e.g. `vpo`: on a marketplace, the `seller` that opens a trip's page by its slug."},"name":{"type":"string","description":"The operator's name, shown on the trip."}},"additionalProperties":false,"description":"The operator that sells the trip: on a direct storefront, the storefront's own operator; on a marketplace, the seller whose network listing it is."},"from_price":{"anyOf":[{"$ref":"#/components/schemas/FromPrice"},{"type":"null"}],"description":"The lowest price per person, as on the trip's page; on every card of the trip list and the search."}},"additionalProperties":false},"description":"Best matches first; for an empty `q`, the newest first."},"total":{"type":"integer","description":"How many trips match, whatever the page.","title":"int"},"page":{"type":"integer","description":"The page given, from 1.","title":"int"},"facets":{"type":"object","required":["city","category","template","duration"],"properties":{"city":{"type":"array","items":{"type":"object","required":["value","name","count"],"properties":{"value":{"type":"string","description":"What the filter takes: a slug or a code."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its name in `locale`, else English; null for a code, e.g. a template."},"count":{"type":"integer","description":"How many of the matching trips have it.","title":"int"}},"additionalProperties":false},"description":"The cities the matching trips start in, as `city` takes them, most trips first."},"category":{"type":"array","items":{"type":"object","required":["value","name","count"],"properties":{"value":{"type":"string","description":"What the filter takes: a slug or a code."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its name in `locale`, else English; null for a code, e.g. a template."},"count":{"type":"integer","description":"How many of the matching trips have it.","title":"int"}},"additionalProperties":false},"description":"The categories they are in, as `category` takes them."},"template":{"type":"array","items":{"type":"object","required":["value","name","count"],"properties":{"value":{"type":"string","description":"What the filter takes: a slug or a code."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its name in `locale`, else English; null for a code, e.g. a template."},"count":{"type":"integer","description":"How many of the matching trips have it.","title":"int"}},"additionalProperties":false},"description":"Their templates, e.g. `day_tour`, as `template` takes them."},"duration":{"type":"array","items":{"type":"object","required":["value","name","count"],"properties":{"value":{"type":"string","description":"What the filter takes: a slug or a code."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its name in `locale`, else English; null for a code, e.g. a template."},"count":{"type":"integer","description":"How many of the matching trips have it.","title":"int"}},"additionalProperties":false},"description":"How long they are: `up_to_3h`, `3h_to_6h`, `full_day` (up to a day) and `multi_day`; filter with `duration_min` and `duration_max` (0-180, 181-360, 361-1440, 1441 and more)."}},"additionalProperties":false,"description":"Counts over every matching trip, not only this page."}},"additionalProperties":false},"SearchUnavailableProblem":{"type":"object","required":["type","title","status","code","detail"],"properties":{"type":{"type":"string","enum":["about:blank"],"description":"Always `about:blank`: `code` says what the problem is."},"title":{"type":"string","enum":["Service Unavailable"],"description":"The HTTP status phrase."},"status":{"type":"number","enum":[503],"description":"The HTTP status code."},"code":{"type":"string","enum":["SearchUnavailable"],"description":"What went wrong, by name: the field to branch on."},"detail":{"type":"string","description":"What went wrong, in a sentence of English for developers."}},"additionalProperties":false},"PublicTrip":{"type":"object","required":["id","slug","template","content_locale","locales","title","subtitle","summary","description","highlights","place_label","summary_emphasis","highlight_details","source_rating","duration_minutes","duration_days","duration_nights","languages","difficulty","min_age","timezone","sales_paused","photos","inclusions","options","itinerary","city","region","country","places","categories","seller","extras","from_price"],"properties":{"id":{"type":"string","description":"The trip's id."},"slug":{"type":"string","description":"Its slug, which opens its page in place of the id; on a marketplace, with its `seller`'s slug."},"template":{"type":"string","description":"What kind of trip it is: `day_tour`, `multi_day`, `transfer`, `ticket` or `activity`."},"content_locale":{"type":"string","description":"The language the text is in: `locale` when the trip is published in it, else `en`."},"locales":{"type":"array","items":{"type":"string"},"description":"The languages the trip is published in, English first: for hreflang."},"title":{"type":"string","description":"Its title, in `content_locale`."},"subtitle":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A line under the title; null for none."},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A short paragraph about it, up to 500 characters; null for none."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The full text about it; null for none."},"highlights":{"type":"array","items":{"type":"string"},"description":"Up to 5 lines on what stands out, in the seller's order."},"place_label":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where it goes in a few words, for a card's eyebrow, e.g. `Coast road south of Muscat`; null when unsaid. In English for now."},"summary_emphasis":{"type":"array","items":{"type":"string"},"description":"Words of `summary` to show emphasised, each exactly as it appears there, e.g. `Nizwa`; empty when the summary is translated."},"highlight_details":{"type":"array","items":{"type":"object","required":["icon","title","text"],"properties":{"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A pictogram's name, e.g. `cup` or `walk`; null for the default."},"title":{"type":"string","description":"E.g. `Coffee in a village house`."},"text":{"type":"string","description":"A line about it."}},"additionalProperties":false},"description":"Highlights with a pictogram, a title and a line, in the seller's order. In English for now."},"source_rating":{"anyOf":[{"type":"object","required":["score","count","source","url"],"properties":{"score":{"type":"number","description":"Out of 5, with one decimal, e.g. `4.5`."},"count":{"type":"integer","description":"How many reviews it is from, e.g. `848`.","title":"int"},"source":{"type":"string","description":"Where they were left, e.g. `Vacation Packages Oman`: the seller's own site, not Tour Pingo."},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A page that shows them; null for none."}},"additionalProperties":false},{"type":"null"}],"description":"The trip's rating and review count from travellers who booked with the seller before, on its own site, imported; null for none. Kept apart from reviews left on Tour Pingo, which only travellers who booked here can leave."},"duration_minutes":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"How long it lasts, in minutes, for any trip but a `multi_day` one; null for those, or when unsaid."},"duration_days":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"How many days a `multi_day` trip lasts; null for other trips, or when unsaid."},"duration_nights":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"How many nights a `multi_day` trip lasts, fewer than its days; null for other trips, or when unsaid."},"languages":{"type":"array","items":{"type":"string"},"description":"The languages the trip is guided in."},"difficulty":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`easy`, `moderate` or `challenging`; null when unsaid."},"min_age":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The youngest age that may join; null for any age."},"timezone":{"type":"string","description":"The IANA time zone its starts are in, e.g. `Asia/Muscat` or `Europe/Rome`."},"sales_paused":{"type":"boolean","description":"Shown, but not bookable for now."},"photos":{"type":"array","items":{"type":"object","required":["url","alt","width","height","place_id","title","caption"],"properties":{"url":{"type":"string","description":"Where the photo is served."},"alt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What it shows, for screen readers; null when unsaid."},"width":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"In pixels; null when unknown."},"height":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"In pixels; null when unknown."},"place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The library place it shows (one of `places`, when a stop points at it), for that stop of the day; null for none."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What it shows in a few words, e.g. `Nizwa Souq`, for the photo mosaic; null for none. In English for now."},"caption":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A line about it under the title; null for none. In English for now."}},"additionalProperties":false},"description":"In order; the first is the cover."},"inclusions":{"type":"array","items":{"type":"object","required":["kind","text","icon"],"properties":{"kind":{"type":"string","description":"`included` in the price, `excluded` from it, what to `bring`, or what to know before (`know_before`)."},"text":{"type":"string","description":"The line, e.g. `Bottled water`."},"icon":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A pictogram's name, e.g. `meal`; null for the kind's own."}},"additionalProperties":false},"description":"What the price includes and leaves out, what to bring and what to know, grouped by `kind`."},"options":{"type":"array","items":{"type":"object","required":["id","name","is_default","private","vehicle","sales_paused","min_units","max_units","availability_type","capacity_mode","confirmation","units","policy","traveller_details","pickup","pickup_rules","room_types"],"properties":{"id":{"type":"string","description":"The option's id, as quotes, holds and the calendar take it."},"name":{"type":"string","description":"Its name as travellers see it."},"is_default":{"type":"boolean","description":"The option shown first."},"private":{"type":"boolean","description":"A private tour for the group alone."},"vehicle":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The vehicle as the seller writes it, e.g. `Private 4WD`; null when unsaid."},"sales_paused":{"type":"boolean","description":"Shown, but not bookable for now."},"min_units":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The fewest units a booking needs in all; null for no limit."},"max_units":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The most units a booking may have in all; null for no limit."},"availability_type":{"type":"string","description":"How it runs: at set `start_time`s, on `fixed_departure` dates, or any time within `opening_hours`."},"capacity_mode":{"type":"string","description":"`on_request` options are booked through checkout with no hold."},"confirmation":{"type":"string","description":"`instant` confirms at once; `manual` waits for the operator; `instant_then_manual` is instant when the operator's rules pass."},"units":{"type":"array","items":{"type":"object","required":["id","type","label","min_age","max_age","min_qty","max_qty","takes_seat","free"],"properties":{"id":{"type":"string","description":"The unit's id, as a quote's `units[].unit_id` takes it."},"type":{"type":"string","description":"What it counts: an age band, e.g. `adult` or `child`, a `group`, a `vehicle`, a `room`, or `other`."},"label":{"type":"string","description":"Its name as travellers see it, e.g. `Adult`."},"min_age":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The youngest age it allows; null for no limit."},"max_age":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The oldest age it allows; null for no limit."},"min_qty":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The fewest a booking may have, when it has any; null for no limit."},"max_qty":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The most a booking may have; null for no limit."},"takes_seat":{"type":"boolean","description":"Takes a seat of its own; false for a baby on a lap."},"free":{"type":"boolean","description":"Travels free, whatever the date."}},"additionalProperties":false},"description":"What can be booked: kinds of traveller, vehicles or groups, each priced and counted on its own."},"policy":{"type":"object","required":["name","tiers"],"properties":{"name":{"type":"string","description":"The policy's name, e.g. `Flexible`."},"tiers":{"type":"array","items":{"type":"object","required":["from_days","to_days","refund_pct"],"properties":{"from_days":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The fewest whole days before the start it covers."},"to_days":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The most whole days before the start it covers; null for no limit."},"refund_pct":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The percent of the price refunded, 0 to 100."}},"additionalProperties":false},"description":"The refund for cancelling at each distance from the start."}},"additionalProperties":false,"description":"The cancellation policy that applies if booked now."},"traveller_details":{"allOf":[{"$ref":"#/components/schemas/PublicTravellerDetails"}],"description":"Which traveller details the option asks, of whom, and when: its own setting, else the trip's, else the lead only. Never a traveller's own data."},"pickup":{"anyOf":[{"$ref":"#/components/schemas/PublicOptionPickup"},{"type":"null"}],"description":"How the option handles pickup (issue #340): `pickup` or `both` picks travellers up where they stay; null when it has no pickup settings. A place's own answer comes from `GET /v1/public/options/{option_id}/pickup/resolve`."},"pickup_rules":{"type":"array","items":{"$ref":"#/components/schemas/PublicPickupRule"},"description":"Its pickup plan's rules by name and price, in the plan's order, then everywhere else (issue #401): the list under the pickup field. Empty when it doesn't pick up. Never where a rule is: which one a place gets is the resolver's answer."},"room_types":{"type":"array","items":{"$ref":"#/components/schemas/PublicRoomType"},"description":"How the group can sleep on a trip of several days sold by room (issue #592), in the seller's order; empty when it isn't. The price is per person sharing; with room types, a quote and a hold need `rooms` that sleep everyone who takes a seat."}},"additionalProperties":false},"description":"The ways to book it, each with its units, cancellation policy, traveller details and pickup; `is_default` marks the one shown first."},"itinerary":{"anyOf":[{"type":"object","required":["days"],"properties":{"subject_to_change":{"type":"boolean","description":"True shows a note that the plan may change, e.g. with the weather; true by default."},"notes":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"Notes for travellers about the whole trip, up to 2000 characters; null for none."},"days":{"type":"array","items":{"type":"object","required":["stops"],"properties":{"title":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"The day's title, e.g. `Arrival and old town walk`; null for none."},"story":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"The day in the seller's own words, up to 5000 characters; null for none."},"meals":{"type":"object","required":[],"properties":{"breakfast":{"type":"string","enum":["included","optional","not_included","none"],"description":"Breakfast: `included`, `optional` (paid for if taken), `not_included`, or `none` when the day has no such meal; `none` by default."},"lunch":{"type":"string","enum":["included","optional","not_included","none"],"description":"Lunch: `included`, `optional` (paid for if taken), `not_included`, or `none` when the day has no such meal; `none` by default."},"dinner":{"type":"string","enum":["included","optional","not_included","none"],"description":"Dinner: `included`, `optional` (paid for if taken), `not_included`, or `none` when the day has no such meal; `none` by default."}},"additionalProperties":false,"description":"The day's meals; none by default."},"stops":{"type":"array","items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["pickup","visit","activity","photo_stop","pass_by","meal","border","dropoff"],"description":"What the stop is: a `pickup`, a `visit`, an `activity`, a `photo_stop`, a place passed by (`pass_by`), a `meal`, a `border` crossing or the `dropoff`."},"place_id":{"anyOf":[{"type":"string","description":"a Universally Unique Identifier","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},{"type":"null"}],"description":"The library place it is at, by id; null for a point given by `label`."},"label":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"The stop's name when it has no place, up to 200 characters; null otherwise."},"pin":{"anyOf":[{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number","description":"Latitude, -90 to 90.","title":"between(-90, 90)","minimum":-90,"maximum":90},"lng":{"type":"number","description":"Longitude, -180 to 180.","title":"between(-180, 180)","minimum":-180,"maximum":180}},"additionalProperties":false},{"type":"null"}],"description":"Where a stop with no place is, to put it on the map; null for none."},"title":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"A title for this trip's stop, up to 200 characters; null to use the place's."},"description":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"What happens here on this trip, up to 2000 characters; null to use the place's."},"hide_photo":{"type":"boolean","description":"True shows no photo for the stop; false by default."},"time":{"anyOf":[{"type":"object","required":["from"],"properties":{"from":{"type":"string","description":"The time it starts, `HH:MM`.","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"to":{"anyOf":[{"type":"string","description":"a string matching the pattern ^([01]\\d|2[0-3]):[0-5]\\d$","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},{"type":"null"}],"description":"The time it ends, `HH:MM`, for a range; null for a single time."}},"additionalProperties":false},{"type":"null"}],"description":"When, in the trip's time zone, as a time or a range; null when not said. Never with `slot`."},"slot":{"anyOf":[{"type":"string","enum":["morning","midday","afternoon","evening","night"]},{"type":"null"}],"description":"A rough time of day instead of a clock time: `morning`, `midday`, `afternoon`, `evening` or `night`; null for none. Never with `time`."},"approx":{"type":"boolean","description":"True when the time is approximate; false by default."},"duration_min":{"anyOf":[{"$ref":"#/components/schemas/Int","description":"a number between 1 and 1440","title":"between(1, 1440)","minimum":1,"maximum":1440},{"type":"null"}],"description":"How long it lasts, in minutes, 1 to 1440; null when not said."},"what":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"What travellers do there, e.g. `Swim in the turquoise pools`; null for none."},"includes":{"type":"array","items":{"type":"string","description":"a string that will be trimmed"},"description":"What the stop includes, e.g. `Coffee and dates`, up to 10; they also go in what's included. None by default.","title":"maxItems(10)","maxItems":10},"included_line":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"How the stop reads in what's included, when not its name; null for the default."},"access":{"type":"string","enum":["visit","pass_by","photo_stop","outside_only"],"description":"How travellers see it: `visit`, `pass_by`, `photo_stop`, or `outside_only`; by default from the stop's type."},"inclusion":{"type":"string","enum":["included","optional","ticket_extra","free"],"description":"Whether its price includes it: `included` (the default), `optional`, `ticket_extra` (the entry ticket is extra) or `free`."},"note":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"A note for travellers, up to 500 characters; null for none."},"leg":{"anyOf":[{"type":"object","required":[],"properties":{"mode":{"type":"string","enum":["drive","4wd","walk","boat","train","flight","transfer"],"description":"How they travel: `drive` (the default), `4wd`, `walk`, `boat`, `train`, `flight` or `transfer`."},"slot":{"anyOf":[{"type":"string","enum":["morning","midday","afternoon","evening","night"]},{"type":"null"}],"description":"A rough time of day instead of a clock time: `morning`, `midday`, `afternoon`, `evening` or `night`; null for none."},"title":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"A name for the leg when it matters, e.g. `Over the Hajar Mountains`; null for none."},"text":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"A few words about the leg, up to 1000 characters; null for none."},"duration_min":{"anyOf":[{"$ref":"#/components/schemas/Int","description":"a number between 1 and 1440","title":"between(1, 1440)","minimum":1,"maximum":1440},{"type":"null"}],"description":"How long it takes, in minutes, 1 to 1440; null when not said."},"distance_km":{"anyOf":[{"type":"number","description":"a number less than 1000000","title":"lessThan(1000000)","exclusiveMinimum":0,"exclusiveMaximum":1000000},{"type":"null"}],"description":"How far it is, in km; null when not said."}},"additionalProperties":false},{"type":"null"}],"description":"How travellers get here from the stop before, or from the night's stay; null when not said."}},"additionalProperties":false},"description":"The day's stops in order, up to 40.","title":"maxItems(40)","maxItems":40}},"additionalProperties":false},"description":"The days in order, 1 to 60.","title":"maxItems(60)","minItems":1,"maxItems":60},"stays":{"type":"array","items":{"type":"object","required":["from_night","to_night","kind"],"properties":{"from_night":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 1 and 59","title":"between(1, 59)","minimum":1,"maximum":59}],"description":"The first night it covers, 1 to 59: night 1 follows day 1."},"to_night":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 1 and 59","title":"between(1, 59)","minimum":1,"maximum":59}],"description":"The last night it covers, 1 to 59; the same as `from_night` for one night."},"place_id":{"anyOf":[{"type":"string","description":"a Universally Unique Identifier","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},{"type":"null"}],"description":"The library place where the nights are spent, by id; null when not said."},"kind":{"type":"string","enum":["hotel","camp","lodge","guesthouse","boat","train","flight","none"],"description":"Where the nights are spent: a `hotel`, `camp`, `lodge`, `guesthouse`, a `boat`, `train` or `flight`, or `none` for the traveller's own arrangements."},"name":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"The hotel's or camp's name, up to 200 characters; null when not said."},"class":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"Its class, e.g. `4*`; null when not said."},"or_similar":{"type":"boolean","description":"True when a similar one may replace it; true by default."},"note":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"A note for travellers, up to 500 characters; null for none."}},"additionalProperties":false},"description":"Where the nights are spent, each over a range of nights, up to 60; none for a day trip.","title":"maxItems(60)","maxItems":60}},"additionalProperties":false},{"type":"null"}],"description":"The day-by-day plan, in the page's language; null when the trip has none."},"city":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The city it starts in; null when it has no start place or the library doesn't know the place's city."},"region":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"That city's region; null with the city."},"country":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The country it starts in; null when it has no start place."},"places":{"type":"array","items":{"type":"object","required":["id","slug","type","name","lat","lng","city"],"properties":{"id":{"type":"string","description":"The place's id in the places library."},"slug":{"type":"string","description":"Its slug, e.g. `wadi-shab`: its page's address, and what the `place` and `via` filters take."},"type":{"type":"string","description":"What kind of place it is, e.g. `wadi`, `fort` or `city`."},"name":{"type":"string","description":"Its name in `locale` when translated, else English."},"lat":{"type":"number","description":"Its latitude, in decimal degrees."},"lng":{"type":"number","description":"Its longitude, in decimal degrees."},"city":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The city it is in, which may not be the trip's; null when the library doesn't know it."}},"additionalProperties":false},"description":"The places its stops and stays point at."},"categories":{"type":"array","items":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},"description":"The platform categories it is in, main one first, each with its slug (as the `category` filter takes it) and its name in `locale`, else English."},"seller":{"type":"object","required":["slug","name","base","blurb"],"properties":{"slug":{"type":"string","description":"The operator's slug, e.g. `vpo`: on a marketplace, the `seller` that opens a trip's page by its slug."},"name":{"type":"string","description":"The operator's name, shown on the trip."},"base":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where the seller is based, as it writes it, e.g. `Seeb, Muscat, Oman`; null when unsaid."},"blurb":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What the seller does, in a line, e.g. `Day trips and packages across Oman`; null when unsaid."}},"additionalProperties":false,"description":"The operator that sells the trip, as on the cards, with where it is based and what it does."},"extras":{"type":"array","items":{"$ref":"#/components/schemas/PublicExtra"},"description":"What can be added to it, in the seller's order, priced as a quote would; each says which options offer it. Choose them on a quote with `extras`."},"from_price":{"anyOf":[{"$ref":"#/components/schemas/FromPrice"},{"type":"null"}],"description":"The lowest price per person the trip can be booked at, from the quote engine; null when it has no published price (ask the operator)."}},"additionalProperties":false},"PublicTravellerDetails":{"type":"object","required":["scope","fields","collect","due_hours_before"],"properties":{"scope":{"type":"string","enum":["lead","all"],"description":"`lead`: only the lead's name, email and phone, at checkout, and `fields` is empty. `all`: every traveller gives the `fields` that ask them."},"fields":{"type":"array","items":{"type":"object","required":["key","required","unit_types"],"properties":{"key":{"type":"string","enum":["full_name","date_of_birth","nationality","passport","gender","weight_height","dietary","medical","room"],"description":"full_name; date_of_birth; nationality; passport (number and expiry); gender; weight_height (kg and cm); dietary; medical (notes for operations staff); room (hotel room, for pickups)."},"required":{"type":"boolean","description":"Whether checkout (or the deadline) needs it; an optional field may be left out."},"unit_types":{"type":"array","items":{"type":"string","enum":["adult","child","infant","senior","youth","student","group","vehicle","room","other"],"description":"What the unit counts: an age band (`adult`, `child`, `infant`, `senior`, `youth`, `student`), a `group`, a `vehicle`, a `room`, or `other`."},"description":"Asked only of travellers of these unit types, e.g. `adult`; empty for every traveller."}},"additionalProperties":false},"description":"The fields asked, in the spec's order; with `all`, always `full_name`, required of everyone."},"collect":{"type":"string","enum":["checkout","after_booking"],"description":"`checkout`: due at booking, so checkout refuses with `TravellerDetailsRequired` without the required fields; ask them before the first submit. `after_booking`: the lead books alone, and the details are due `due_hours_before` the start."},"due_hours_before":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"For `after_booking`, how many hours before the start the details are due, e.g. 48; null when they are due at checkout."}},"additionalProperties":false},"PublicOptionPickup":{"type":"object","required":["mode","latest_change_hours"],"properties":{"mode":{"type":"string","enum":["pickup","meeting_point","both"],"description":"How travellers reach the start: `pickup`, the `meeting_point`, or `both` (pickup, or meet there with no pickup charge)."},"latest_change_hours":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The pickup can be added or changed until this many hours before the start, e.g. `24`."}},"additionalProperties":false},"PublicPickupRule":{"type":"object","required":["name","kind","outcome","price"],"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The rule's name, e.g. \"Within 20 km of Muttrah\"; null for everywhere else."},"kind":{"type":"string","enum":["point","area","elsewhere"],"description":"`point` = a pickup point, e.g. the airport; `area` = a radius or a drawn area; `elsewhere` = the plan's rule for places outside every area."},"outcome":{"type":"string","enum":["included","surcharge","on_request","not_offered","by_distance"],"description":"As the resolver answers for a place the rule covers: free, a price, on request (the operator confirms it), not offered; `by_distance` = a price per km outside the areas, which the resolver works out."},"price":{"anyOf":[{"type":"object","required":["amount","currency","per"],"properties":{"amount":{"type":"integer","description":"Retail in minor units of `currency`, as the resolver prices it, e.g. 6000 for OMR 6.000.","title":"int"},"currency":{"type":"string","description":"The `currency` asked for when it is one of the storefront's, else its display currency, as `from_price`."},"per":{"type":"string","enum":["vehicle","person","booking"],"description":"What one price covers."}},"additionalProperties":false},{"type":"null"}],"description":"For `surcharge`; null otherwise."},"place":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"The library place's slug, e.g. `muttrah`."},"name":{"type":"string","description":"Its name in `locale` when translated, else English, e.g. `مطرح`."}},"additionalProperties":false},{"type":"null"}],"description":"The library place an `area` is named after, e.g. Muttrah for \"Within 15 km of Muttrah\": the place of that name nearest its centre; null when none is."}},"additionalProperties":false},"PublicRoomType":{"type":"object","required":["id","name","occupancy","sharing","supplement"],"properties":{"id":{"type":"string","description":"As a quote's `rooms[].room_type_id` takes it."},"name":{"type":"string","description":"E.g. `Twin or double` or `Single`."},"occupancy":{"type":"integer","description":"How many sleep in one: 2 for a twin or double, 1 for a single.","title":"int"},"sharing":{"type":"string","enum":["private","shared"],"description":"`shared`: a solo traveller books one bed and is paired later with another traveller of the same gender; asked as beds, not rooms."},"supplement":{"anyOf":[{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","description":"Minor units of `currency`.","title":"int"},"currency":{"type":"string","description":"The ISO 4217 currency code, e.g. `USD`, `EUR` or `OMR`."}},"additionalProperties":false},{"type":"null"}],"description":"What each traveller in it pays on top of the price per person sharing, retail in the `currency` asked for (else the display currency), as a quote's room line prices it: 0 for a twin, the single supplement for a single. Null when the trip has no published price."}},"additionalProperties":false},"PublicExtra":{"type":"object","required":["id","name","description","per","who","included","option_ids","min_age","max_age","unit_types","max_per_booking","pay","price","unit_prices","local_price"],"properties":{"id":{"type":"string","description":"As a quote's `extras[].extra_id` takes it."},"name":{"type":"string","description":"E.g. `Lunch`."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The seller's line about it, e.g. `Served in Wadi Tiwi.`; null for none."},"per":{"type":"string","enum":["person","item","booking","vehicle"],"description":"What one price covers."},"who":{"type":"string","enum":["choose","everyone"],"description":"`choose`: each traveller chooses it; `everyone`: added for everyone it is for."},"included":{"type":"boolean","description":"In the trip's price already."},"option_ids":{"type":"array","items":{"type":"string"},"description":"The options it is offered on; empty for every option."},"min_age":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The youngest age it is for; null for any age."},"max_age":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The oldest age it is for; null for any age."},"unit_types":{"type":"array","items":{"type":"string"},"description":"Who may have it, by age band, e.g. `adult`; empty for every traveller."},"max_per_booking":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"The most one booking may have; null for no limit."},"pay":{"type":"string","enum":["upfront","local"],"description":"`upfront`: paid with the booking; `local`: paid on the day, never online."},"price":{"anyOf":[{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","description":"Minor units of `currency`.","title":"int"},"currency":{"type":"string","description":"The ISO 4217 currency code, e.g. `USD`, `EUR` or `OMR`."}},"additionalProperties":false},{"type":"null"}],"description":"Per one, retail in the `currency` asked for (else the display currency), as a quote's line would price it; null when included, paid on the day, or the trip has no published price."},"unit_prices":{"type":"object","required":[],"properties":{},"additionalProperties":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","description":"Minor units of `currency`.","title":"int"},"currency":{"type":"string","description":"The ISO 4217 currency code, e.g. `USD`, `EUR` or `OMR`."}},"additionalProperties":false},"description":"Age bands that pay another price than `price`, e.g. `{\"child\": …}`."},"local_price":{"anyOf":[{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","description":"Minor units of `currency`.","title":"int"},"currency":{"type":"string","description":"The ISO 4217 currency code, e.g. `USD`, `EUR` or `OMR`."}},"additionalProperties":false},{"type":"null"}],"description":"For `local`: per one, in the currency paid on the day."}},"additionalProperties":false},"PublicCityList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","required":["slug","name","content_locale","region","country","world_region","lat","lng","trip_count","seller_count"],"properties":{"slug":{"type":"string","description":"Its slug, e.g. `muscat`, as the trip list's `city` filter takes it."},"name":{"type":"string","description":"Its name in `locale` when translated, else English."},"content_locale":{"type":"string","description":"The language `name` is in: `locale` when translated, else `en`."},"region":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false,"description":"The region the city is in."},"country":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false,"description":"The country the city is in."},"world_region":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The part of the world its country is in, e.g. `middle-east` (Middle East), for grouping destinations; null when the library hasn't placed the country."},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The city's latitude, in decimal degrees; null when the library has no point for it."},"lng":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"The city's longitude, in decimal degrees; null when the library has no point for it."},"trip_count":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many of the storefront's published trips start in it."},"seller_count":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many sellers those trips are from: 1 on a direct storefront."}},"additionalProperties":false},"description":"Cities with the most trips first, then by name."}},"additionalProperties":false},"PublicSellerList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","required":["slug","name","country","trip_count","base","blurb","rating"],"properties":{"slug":{"type":"string","description":"The operator's slug, e.g. `vpo`, as a trip's `seller.slug` and the trip list's `seller` filter take it."},"name":{"type":"string","description":"The operator's name, as on its trips."},"country":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The country it is based in, named in `locale` when translated, else English; null when the library doesn't have it."},"trip_count":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many of its published trips the storefront sells: on a marketplace, its approved listings."},"base":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where it is based, as it writes it, e.g. `Seeb, Muscat, Oman`; null when unsaid."},"blurb":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What it does, in a line, e.g. `Day trips and packages across Oman`; null when unsaid."},"rating":{"anyOf":[{"type":"object","required":["score","count"],"properties":{"score":{"type":"number","description":"The average out of 5, weighted by reviews, to one decimal, e.g. `4.7`."},"count":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many reviews it is from, in all."}},"additionalProperties":false},{"type":"null"}],"description":"Its published trips' ratings on its own site, as one: `score`, the average weighted by reviews, to one decimal, and `count`, the reviews in all; null when none is rated."}},"additionalProperties":false},"description":"The most trips first, then by name; only sellers with trips."}},"additionalProperties":false},"PublicCategoryList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","required":["slug","name","content_locale","trip_count"],"properties":{"slug":{"type":"string","description":"Its slug, e.g. `desert-safari`, as the trip list's `category` filter takes it."},"name":{"type":"string","description":"Its name in `locale` when translated, else English."},"content_locale":{"type":"string","description":"The language `name` is in: `locale` when translated, else `en`."},"trip_count":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many of the storefront's published trips are in it."}},"additionalProperties":false},"description":"In the platform's order; only categories with trips."}},"additionalProperties":false},"PublicCollectionList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","required":["slug","name","content_locale"],"properties":{"slug":{"type":"string","description":"Its slug, e.g. `evenings-out`, as the trip list's `collection` takes it."},"name":{"type":"string","description":"Its name in `locale` when platform staff wrote it there, else English."},"content_locale":{"type":"string","description":"The language `name` is in: `locale` when written there, else `en`."}},"additionalProperties":false},"description":"In the order platform staff set; the home shows the first 3 with trips, each as a rail of its own. Empty on a direct storefront."}},"additionalProperties":false},"PublicPlaceList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","slug","type","name","lat","lng","city","area","country","photos"],"properties":{"id":{"type":"string","description":"The place's id in the places library."},"slug":{"type":"string","description":"Its slug, e.g. `wadi-shab`: its page's address, and what the `place` and `via` filters take."},"type":{"type":"string","description":"What kind of place it is, e.g. `wadi`, `fort` or `city`."},"name":{"type":"string","description":"Its name in `locale` when translated, else English."},"lat":{"type":"number","description":"Its latitude, in decimal degrees."},"lng":{"type":"number","description":"Its longitude, in decimal degrees."},"city":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The city it is in, which may not be the trip's; null when the library doesn't know it."},"area":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The locality as sellers name it, e.g. `Izki`: in `locale` when it is the place's city or a library place of that name (إزكي), else as written."},"country":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The country it is in."},"photos":{"type":"array","items":{"type":"object","required":["url","alt","width","height"],"properties":{"url":{"type":"string","description":"Where the photo is served."},"alt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What it shows, for screen readers; null when unsaid."},"width":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"In pixels; null when unknown."},"height":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"In pixels; null when unknown."}},"additionalProperties":false},"description":"Up to 4 photos of it from the storefront's trips, which their sellers tagged with it, the trips' covers first; empty when none is (issue #459). Its page has more."}},"additionalProperties":false},"description":"By name; each named in `locale` when translated, else English."}},"additionalProperties":false},"PublicPlace":{"type":"object","required":["id","slug","type","name","lat","lng","city","region","country","content_locale","area","summary","body","seo_title","seo_description","photos"],"properties":{"id":{"type":"string","description":"The place's id in the places library."},"slug":{"type":"string","description":"Its slug, e.g. `wadi-shab`: its page's address, and what the `place` and `via` filters take."},"type":{"type":"string","description":"What kind of place it is, e.g. `wadi`, `fort` or `city`."},"name":{"type":"string","description":"Its name in `locale` when translated, else English."},"lat":{"type":"number","description":"Its latitude, in decimal degrees."},"lng":{"type":"number","description":"Its longitude, in decimal degrees."},"city":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The city it is in; null when the library doesn't know it."},"region":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"That city's region; null with the city."},"country":{"anyOf":[{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Its slug; for a country, its ISO 3166-1 alpha-2 code in lower case, e.g. `om`."},"name":{"type":"string","description":"Its name in `locale`, else English."}},"additionalProperties":false},{"type":"null"}],"description":"The country it is in."},"content_locale":{"type":"string","description":"The language the name and text are in: `locale` when the place is translated into it, else `en`."},"area":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The locality as sellers name it, e.g. `Muttrah`."},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A line or two about it, up to 500 characters."},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The longer text about it."},"seo_title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The page's title for search engines; null when the library sets none."},"seo_description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The page's description for search engines; null when the library sets none."},"photos":{"type":"array","items":{"type":"object","required":["url","alt","width","height"],"properties":{"url":{"type":"string","description":"Where the photo is served."},"alt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What it shows, for screen readers; null when unsaid."},"width":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"In pixels; null when unknown."},"height":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"In pixels; null when unknown."}},"additionalProperties":false},"description":"Up to 24 photos of it from the storefront's trips, which their sellers tagged with it (a trip photo's `place_id`), the trips' covers first, the first being its cover; empty when none is (issue #459)."}},"additionalProperties":false},"PublicQuoteRequest":{"type":"object","required":["option_id","start","units"],"properties":{"option_id":{"type":"string","description":"The option to quote."},"start":{"type":"string","description":"The start, in the trip's time zone: a date, or a date and time for a start time, e.g. `2026-10-07` or `2026-10-07T08:00`.","pattern":"^\\d{4}-\\d{2}-\\d{2}(T([01]\\d|2[0-3]):[0-5]\\d)?$"},"units":{"type":"array","items":{"type":"object","required":["unit_id","qty"],"properties":{"unit_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The unit, one of the option's: a kind of traveller, a vehicle or a group."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many, 0 to 999."},"ages":{"type":"array","items":{"$ref":"#/components/schemas/Int","description":"a number between 0 and 120","title":"between(0, 120)","minimum":0,"maximum":120},"description":"Each traveller's age in years, when the trip asks for them."}},"additionalProperties":false},"description":"The travellers, by unit: 1 to 20 entries.","title":"maxItems(20)","minItems":1,"maxItems":20},"extras":{"type":"array","items":{"type":"object","required":["extra_id","qty"],"properties":{"extra_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The extra, one of the trip's."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many, 0 to 999."}},"additionalProperties":false},"description":"Extras to add; none by default."},"travellers":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 1 and 999","title":"between(1, 999)","minimum":1,"maximum":999}],"description":"For vehicle and group units: how many travellers ride, 1 to 999."},"currency":{"type":"string","description":"The ISO 4217 currency to price in, e.g. `EUR`: one the storefront offers, its display currency by default (USD in staff's preview).","pattern":"^[A-Z]{3}$"},"pickup":{"allOf":[{"$ref":"#/components/schemas/PickupRequest"}],"description":"Where to collect the travellers, for an option with a pickup plan."},"rooms":{"type":"array","items":{"type":"object","required":["room_type_id","qty"],"properties":{"room_type_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The room type, one of the option's `room_types`."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many rooms of the type, or beds in it when it is shared, 0 to 999."}},"additionalProperties":false},"description":"For an option with room types (its `room_types`): how the group sleeps, as rooms, or beds in a shared room, up to 10 entries. They must sleep every traveller who takes a seat; needed when the option has room types.","title":"maxItems(10)","maxItems":10}},"additionalProperties":false},"UUID":{"type":"string","description":"a Universally Unique Identifier","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"PickupRequest":{"type":"object","required":[],"properties":{"place_id":{"type":"string","description":"A places library place, e.g. the airport, as `GET /v1/public/places/search` finds it. Send this, or `lat` and `lng`.","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"lat":{"type":"number","description":"Latitude of a map pin, e.g. `23.599`. Send with `lng`.","title":"between(-90, 90)","minimum":-90,"maximum":90},"lng":{"type":"number","description":"Longitude of a map pin, e.g. `58.545`. Send with `lat`.","title":"between(-180, 180)","minimum":-180,"maximum":180},"details":{"allOf":[{"$ref":"#/components/schemas/PickupDetails"}],"description":"Answers to what the matched pickup rule asks; none by default."}},"additionalProperties":false},"PickupDetails":{"type":"object","required":[],"properties":{"flight":{"type":"string","description":"When the rule asks for `flight`: the flight number and arrival time, e.g. `WY 102, 06:15`."},"ship":{"type":"string","description":"When the rule asks for `ship`: the ship's name and all-aboard time, e.g. `AIDAprima, 17:30`."},"room":{"type":"string","description":"When the rule asks for `room`: the room number, e.g. `412`."},"note":{"type":"string","description":"When the rule asks for `note`: a note for the driver, e.g. \"White gate next to the mosque\"."}},"additionalProperties":false,"description":"Answers to what the matched pickup rule asks. Only the asked ones are kept; `flight`, `ship` and `room` are needed by checkout when asked, `note` never is."},"Quote":{"type":"object","required":["option_id","lines","totals","confirmation","pending_expires_at","remaining","pax","pairing","vehicles","fx","policy","pickup"],"properties":{"option_id":{"type":"string","description":"The option quoted."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"},"description":"What is priced, one line per unit, room, extra and pickup."},"totals":{"type":"object","required":["due_now","pay_locally","grand_total"],"properties":{"due_now":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What is paid when booking."},"pay_locally":{"type":"array","items":{"$ref":"#/components/schemas/Money"},"description":"What is paid on the day, per currency."},"grand_total":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"For display."}},"additionalProperties":false,"description":"What the traveller pays, now and on the day."},"confirmation":{"type":"string","enum":["instant","manual"],"description":"`manual`: the operator confirms the booking."},"pending_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When a booking left pending expires."},"remaining":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"Places left before this booking, when counted."},"pax":{"type":"integer","description":"How many travellers, counted from the units.","title":"int"},"pairing":{"type":"boolean","description":"True when a solo traveller takes a bed in a shared room (issue #592) and is paired later with another traveller of the same gender; false otherwise, and always false on an option without room types."},"vehicles":{"type":"array","items":{"type":"object","required":["seats","travellers"],"properties":{"seats":{"type":"integer","description":"The vehicle's seats.","title":"int"},"travellers":{"type":"integer","description":"The travellers riding in it.","title":"int"}},"additionalProperties":false},"description":"The vehicles the group rides in, when counted."},"fx":{"type":"object","required":["base","quote","rate","rate_id","as_of"],"properties":{"base":{"type":"string","enum":["USD"],"description":"Always `USD`, the system currency prices are kept in."},"quote":{"type":"string","description":"The currency quoted in, an ISO 4217 code."},"rate":{"type":"string","description":"How many of `quote` make one US dollar, as an exact decimal string, e.g. `0.92`."},"rate_id":{"type":"string","description":"The rate used."},"as_of":{"type":"string","description":"When the rate was set."}},"additionalProperties":false,"description":"The exchange rate the quote was converted at."},"policy":{"allOf":[{"$ref":"#/components/schemas/PolicySnapshot"}],"description":"The cancellation policy that would apply to the booking."},"pickup":{"anyOf":[{"$ref":"#/components/schemas/BookedPickup"},{"type":"null"}],"description":"The pickup asked for, resolved by the option's pickup plan; null without one."}},"additionalProperties":false,"description":"A price: what the traveller pays, line by line, in the quote's currency, with the policy and pickup that come with it."},"QuoteLine":{"type":"object","required":["kind","ref","label","qty","unit_price","original_unit_price","total","pay_at","included","vehicle"],"properties":{"kind":{"type":"string","enum":["unit","room","extra","pickup","adjustment","offer"],"description":"What it prices: a `unit` (a kind of traveller, a vehicle or a group), a `room`, an `extra`, the `pickup`, an `adjustment`, a line staff added to a booking they made for the traveller (issue #515), with its reason as the label (a discount is negative), or an `offer`, the seller's price for the group from an offer (docs/specs/price-offers.md), in place of the units' and rooms' lines."},"ref":{"type":"string","description":"The unit, room type, extra or pickup it prices; `adjustment-1`, `adjustment-2`, … for an adjustment; `offer` for an offer."},"label":{"type":"string","description":"Its name as travellers see it, e.g. `Adult`; an adjustment's reason."},"qty":{"type":"integer","description":"How many.","title":"int"},"unit_price":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"The price of one, converted and rounded; totals add up exactly."},"original_unit_price":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"A special's price before its discount, to show struck through."},"total":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"`unit_price` times `qty`."},"pay_at":{"type":"string","enum":["upfront","local"],"description":"`local`: paid on the day, in its own currency, never charged online."},"included":{"type":"boolean","description":"True for an extra the trip includes, at no charge."},"vehicle":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"Which vehicle, when the group is split over several."}},"additionalProperties":false},"PolicySnapshot":{"type":"object","required":["name","preset","tiers"],"properties":{"name":{"type":"string","description":"The policy's name, e.g. `Flexible`."},"preset":{"type":"string","enum":["flexible","moderate","strict","non_refundable","custom"],"description":"`flexible`, `moderate`, `strict` or `non_refundable`, the standard presets' tiers, or `custom` tiers."},"tiers":{"type":"array","items":{"$ref":"#/components/schemas/RefundTier"},"description":"Its refund tiers, which cover every day before the start once."}},"additionalProperties":false,"description":"The cancellation policy the booking keeps as sold: later changes to the policy never change it."},"RefundTier":{"type":"object","required":["from_days","refund_pct"],"properties":{"from_days":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 365","title":"between(0, 365)","minimum":0,"maximum":365}],"description":"The fewest whole days before the start it covers, 0 to 365."},"to_days":{"anyOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 365","title":"between(0, 365)","minimum":0,"maximum":365},{"type":"null"}],"description":"The most whole days before the start it covers, 0 to 365; null for no limit."},"refund_pct":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 100","title":"between(0, 100)","minimum":0,"maximum":100}],"description":"The percent of the price refunded, 0 to 100."}},"additionalProperties":false,"description":"A refund percent for cancelling between `from_days` and `to_days` whole days before the start; no `to_days` is open-ended."},"BookedPickup":{"type":"object","required":["input","place_id","name","lat","lng","outcome","match_kind","rule_id","rule_name","distance_km","price","price_per","time_offset_min","window","on_request","status","asks","details","missing","explanation"],"properties":{"input":{"type":"string","enum":["place","pin"],"description":"How the place was given: a library `place` or a map `pin`."},"place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The places library place; null for a pin."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The place's name, in English; null for a pin."},"lat":{"type":"number","description":"Latitude of the place or pin."},"lng":{"type":"number","description":"Longitude of the place or pin."},"outcome":{"type":"string","enum":["included","surcharge","on_request"],"description":"`included` = free; `surcharge` = a `pickup` line prices it; `on_request` = the operator confirms it and its price later, and the booking goes ahead meanwhile (P3)."},"match_kind":{"type":"string","enum":["point","radius","area","everywhere_else"],"description":"Which kind of rule gave the answer."},"rule_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The rule that won; null for everywhere else."},"rule_name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The winning rule's name; null for everywhere else."},"distance_km":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Straight-line km, one decimal, e.g. `\"0.9\"`; null for a point or a drawn area."},"price":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"The retail price per `price_per` in the quote's currency; zero when included; null on request."},"price_per":{"anyOf":[{"type":"string","enum":["vehicle","person","booking"],"description":"What one price covers: a vehicle (private tours), a person or a booking."},{"type":"null"}],"description":"What `price` covers; null when free or on request."},"time_offset_min":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"Minutes before the start the pickup begins; null on request everywhere else."},"window":{"anyOf":[{"$ref":"#/components/schemas/PickupWindow"},{"type":"null"}],"description":"When to be ready, as a window; null for a start with no time, or while the operator confirms an on-request pickup."},"on_request":{"type":"boolean","description":"The operator confirms this pickup and its price; never the booking itself (P3)."},"status":{"type":"string","enum":["set","to_confirm"],"description":"`set`; or `to_confirm` while the operator confirms an on-request pickup."},"asks":{"type":"array","items":{"type":"string","enum":["flight","ship","room","note"],"description":"A detail asked only when the rule is chosen: `flight` (number and arrival time), `ship` (name and all-aboard time), `room` (room number) or `note` (free text)."},"description":"What the rule asks, e.g. `[\"flight\"]`."},"details":{"allOf":[{"$ref":"#/components/schemas/PickupDetails"}],"description":"The details given for what the rule asks."},"missing":{"type":"array","items":{"type":"string","enum":["flight","ship","room","note"],"description":"A detail asked only when the rule is chosen: `flight` (number and arrival time), `ship` (name and all-aboard time), `room` (room number) or `note` (free text)."},"description":"Asked details still missing; checkout refuses with `PickupDetailsRequired` until they are given."},"explanation":{"type":"string","description":"One plain English sentence saying why, with the price as the traveller pays it."}},"additionalProperties":false,"description":"The pickup as quoted, and as the booking keeps it: later plan changes never change it (P2)."},"PickupWindow":{"type":"object","required":["date","from","to"],"properties":{"date":{"type":"string","description":"The pickup date, e.g. `2026-11-13`."},"from":{"type":"string","description":"The start time less the rule's `time_offset_min`, e.g. `07:10`."},"to":{"type":"string","description":"The plan's `window_min` later (10 minutes unless it sets another), e.g. `07:20`. The exact time is confirmed the evening before."}},"additionalProperties":false,"description":"When to be ready, as a window; null for a start with no time, or while the operator confirms an on-request pickup."},"QuoteRefusedProblem":{"type":"object","required":["type","title","status","code","detail","details"],"properties":{"type":{"type":"string","enum":["about:blank"],"description":"Always `about:blank`: `code` says what the problem is."},"title":{"type":"string","enum":["Unprocessable Content"],"description":"The HTTP status phrase."},"status":{"type":"number","enum":[422],"description":"The HTTP status code."},"code":{"type":"string","description":"Why it was refused, e.g. `PriceNotAvailable`, `NotEnoughCapacity`, `AgeRestriction`, `VehicleLimitReached`, `CurrencyNotEnabled`."},"detail":{"type":"string","description":"What went wrong, in a sentence of English for developers."},"details":{"type":"object","required":[],"properties":{},"additionalProperties":{"$id":"/schemas/unknown","title":"unknown"},"description":"The refusal's fields, e.g. the `unit_id` and the ages allowed for `AgeRestriction`."}},"additionalProperties":false},"PublicHoldRequest":{"type":"object","required":["option_id","start","units"],"properties":{"option_id":{"type":"string","description":"The option to quote."},"start":{"type":"string","description":"The start, in the trip's time zone: a date, or a date and time for a start time, e.g. `2026-10-07` or `2026-10-07T08:00`.","pattern":"^\\d{4}-\\d{2}-\\d{2}(T([01]\\d|2[0-3]):[0-5]\\d)?$"},"units":{"type":"array","items":{"type":"object","required":["unit_id","qty"],"properties":{"unit_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The unit, one of the option's: a kind of traveller, a vehicle or a group."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many, 0 to 999."},"ages":{"type":"array","items":{"$ref":"#/components/schemas/Int","description":"a number between 0 and 120","title":"between(0, 120)","minimum":0,"maximum":120},"description":"Each traveller's age in years, when the trip asks for them."}},"additionalProperties":false},"description":"The travellers, by unit: 1 to 20 entries.","title":"maxItems(20)","minItems":1,"maxItems":20},"extras":{"type":"array","items":{"type":"object","required":["extra_id","qty"],"properties":{"extra_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The extra, one of the trip's."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many, 0 to 999."}},"additionalProperties":false},"description":"Extras to add; none by default."},"travellers":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 1 and 999","title":"between(1, 999)","minimum":1,"maximum":999}],"description":"For vehicle and group units: how many travellers ride, 1 to 999."},"currency":{"type":"string","description":"The ISO 4217 currency to price in, e.g. `EUR`: one the storefront offers, its display currency by default (USD in staff's preview).","pattern":"^[A-Z]{3}$"},"pickup":{"allOf":[{"$ref":"#/components/schemas/PickupRequest"}],"description":"Where to collect the travellers, for an option with a pickup plan."},"rooms":{"type":"array","items":{"type":"object","required":["room_type_id","qty"],"properties":{"room_type_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The room type, one of the option's `room_types`."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many rooms of the type, or beds in it when it is shared, 0 to 999."}},"additionalProperties":false},"description":"For an option with room types (its `room_types`): how the group sleeps, as rooms, or beds in a shared room, up to 10 entries. They must sleep every traveller who takes a seat; needed when the option has room types.","title":"maxItems(10)","maxItems":10},"attribution":{"anyOf":[{"$ref":"#/components/schemas/Attribution"},{"type":"null"}],"description":"The campaign and the page that sent the traveller (issue #532), as the site saw them on landing; the seller sees them on the booking. Null or left out when the site has none."}},"additionalProperties":false},"Attribution":{"type":"object","required":[],"properties":{"utm_source":{"type":"string","description":"Who sent the traveller, e.g. `newsletter` or `google`, up to 200 characters; left out when the address had none."},"utm_medium":{"type":"string","description":"How they were sent, e.g. `email` or `cpc`, up to 200 characters; left out when the address had none."},"utm_campaign":{"type":"string","description":"The campaign, e.g. `autumn`, up to 200 characters; left out when the address had none."},"utm_term":{"type":"string","description":"The paid search words, e.g. `desert tour`, up to 200 characters; left out when the address had none."},"utm_content":{"type":"string","description":"Which link or advert, e.g. `banner-top`, up to 200 characters; left out when the address had none."},"referrer":{"type":"string","description":"The page the traveller came from, e.g. `https://www.google.com/`, up to 500 characters; left out unless it was another site."}},"additionalProperties":false},"ConflictProblem":{"type":"object","required":["type","title","status","code","detail"],"properties":{"type":{"type":"string","enum":["about:blank"],"description":"Always `about:blank`: `code` says what the problem is."},"title":{"type":"string","enum":["Conflict"],"description":"The HTTP status phrase."},"status":{"type":"number","enum":[409],"description":"The HTTP status code."},"code":{"type":"string","enum":["Conflict"],"description":"What went wrong, by name: the field to branch on."},"detail":{"type":"string","description":"What went wrong, in a sentence of English for developers."}},"additionalProperties":false},"Hold":{"type":"object","required":["ref","status","hold_expires_at","start","quote"],"properties":{"ref":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"status":{"type":"string","enum":["on_hold","pending","confirmed","completed","cancelled","rejected","expired"],"description":"Where the booking stands: `on_hold` while checkout holds its places, `pending` until the operator confirms it, `confirmed`, `completed` once the trip is over, or `cancelled`, `rejected` or `expired`."},"hold_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the places go back on sale."},"start":{"type":"string","description":"The start held, e.g. `2026-10-07T08:00`."},"quote":{"allOf":[{"$ref":"#/components/schemas/Quote"}],"description":"The price held, re-quoted as the hold was made: what checkout charges."}},"additionalProperties":false},"HoldChoicesRequest":{"type":"object","required":[],"properties":{"extras":{"type":"array","items":{"type":"object","required":["extra_id","qty"],"properties":{"extra_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The extra, one of the trip's."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many, 0 to 999."}},"additionalProperties":false},"description":"Extras to add; none by default."},"pickup":{"allOf":[{"$ref":"#/components/schemas/PickupRequest"}],"description":"Where to collect the travellers, for an option with a pickup plan."}},"additionalProperties":false,"description":"The extras and the pickup the traveller wants, each in place of the hold's own: no `extras` means none, no `pickup` means none."},"CheckoutHold":{"type":"object","required":["ref","lead"],"properties":{"ref":{"type":"string","description":"The hold to check out."},"lead":{"type":"object","required":["name","email","phone"],"properties":{"name":{"type":"string","description":"The lead traveller's name, up to 200 characters."},"email":{"type":"string","description":"Their email: the booking's confirmation and its signed link go to it."},"phone":{"type":"string","description":"Their phone or WhatsApp number with its country code, e.g. `+491512345678`.","pattern":"^\\+[1-9]\\d{6,14}$"}},"additionalProperties":false,"description":"The lead traveller: who the operator contacts about the booking."},"payment_method":{"type":"string","enum":["bank_transfer","pay_on_arrival","payment_link"],"description":"Needed when the storefront has payment methods: one it offers for the start."},"special_requests":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"Anything the operator should know, up to 2000 characters; null for nothing."},"locale":{"type":"string","description":"The traveller's language, e.g. `fr`: their emails use it when the storefront offers it, else the storefront's default.","pattern":"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$","title":"maxLength(35)","maxLength":35},"traveller_details":{"type":"array","items":{"$ref":"#/components/schemas/TravellerInput"},"description":"Each traveller's details, the lead first, when the option asks for them (its `traveller_details`). Needed in full when it asks at checkout; otherwise any given now are kept and the rest are due later.","title":"maxItems(100)","maxItems":100},"pickup_details":{"allOf":[{"$ref":"#/components/schemas/PickupDetails"}],"description":"Answers to what the pickup's rule asks (its `asks`), e.g. the flight: they complete or replace those sent with the hold or the pickup. Checkout refuses with `PickupDetailsRequired` while a `flight`, `ship` or `room` asked for is missing."},"attribution":{"anyOf":[{"$ref":"#/components/schemas/Attribution"},{"type":"null"}],"description":"The campaign and the page that sent the traveller (issue #532), as the site saw them on landing: they replace the hold's; null or left out keeps the hold's."}},"additionalProperties":false},"TravellerInput":{"type":"object","required":["first_name"],"properties":{"unit_type":{"anyOf":[{"type":"string","enum":["adult","child","infant","senior","youth","student","group","vehicle","room","other"],"description":"What the unit counts: an age band (`adult`, `child`, `infant`, `senior`, `youth`, `student`), a `group`, a `vehicle`, a `room`, or `other`."},{"type":"null"}],"description":"Which of the booking's units this traveller is, e.g. child; asked fields can depend on it."},"first_name":{"type":"string","description":"Their first name, or their only one, up to 100 characters."},"last_name":{"type":"string","description":"Empty for someone with one name."},"date_of_birth":{"anyOf":[{"type":"string","description":"a string matching the pattern ^\\d{4}-\\d{2}-\\d{2}$","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"description":"Their date of birth, `YYYY-MM-DD`; sensitive. Null when not asked or not given."},"nationality":{"anyOf":[{"type":"string","description":"a string matching the pattern ^[A-Z]{2}$","pattern":"^[A-Z]{2}$"},{"type":"null"}],"description":"Their nationality, an ISO 3166-1 alpha-2 code in capitals, e.g. `DE`; null when not asked or not given."},"passport_number":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"Their passport number, 5 to 20 letters and digits; sensitive. Null when not asked or not given."},"passport_expiry":{"anyOf":[{"type":"string","description":"a string matching the pattern ^\\d{4}-\\d{2}-\\d{2}$","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"description":"When their passport expires, `YYYY-MM-DD`: after the trip ends. Null when not asked or not given."},"gender":{"anyOf":[{"type":"string","enum":["female","male","other"]},{"type":"null"}],"description":"`female`, `male` or `other`; null when not asked or not given."},"weight_kg":{"anyOf":[{"$ref":"#/components/schemas/Int","description":"a number between 3 and 300","title":"between(3, 300)","minimum":3,"maximum":300},{"type":"null"}],"description":"Their weight in kg, 3 to 300; null when not asked or not given."},"height_cm":{"anyOf":[{"$ref":"#/components/schemas/Int","description":"a number between 40 and 250","title":"between(40, 250)","minimum":40,"maximum":250},{"type":"null"}],"description":"Their height in cm, 40 to 250; null when not asked or not given."},"dietary":{"type":"array","items":{"type":"string","enum":["none","vegetarian","vegan","halal","kosher","gluten_free","lactose_free","nut_allergy","other"]},"description":"What they eat or avoid, each once, e.g. `vegetarian` or `nut_allergy`; none by default.","title":"maxItems(9)","maxItems":9},"dietary_note":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"More about what they eat, up to 300 characters; null for none."},"medical_note":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"What operations staff should know about their health, up to 1000 characters; sensitive. Null for none."},"room":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"Their hotel room, for the pickup, up to 20 characters; null when not asked or not given."}},"additionalProperties":false},"CheckoutOnRequest":{"type":"object","required":["option_id","start","units","lead"],"properties":{"option_id":{"type":"string","description":"An on-request option, booked with no hold."},"start":{"type":"string","description":"The start, in the trip's time zone: a date, or a date and time for a start time, e.g. `2026-10-07` or `2026-10-07T08:00`.","pattern":"^\\d{4}-\\d{2}-\\d{2}(T([01]\\d|2[0-3]):[0-5]\\d)?$"},"units":{"type":"array","items":{"type":"object","required":["unit_id","qty"],"properties":{"unit_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The unit, one of the option's: a kind of traveller, a vehicle or a group."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many, 0 to 999."},"ages":{"type":"array","items":{"$ref":"#/components/schemas/Int","description":"a number between 0 and 120","title":"between(0, 120)","minimum":0,"maximum":120},"description":"Each traveller's age in years, when the trip asks for them."}},"additionalProperties":false},"description":"The travellers, by unit: 1 to 20 entries.","title":"maxItems(20)","minItems":1,"maxItems":20},"extras":{"type":"array","items":{"type":"object","required":["extra_id","qty"],"properties":{"extra_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The extra, one of the trip's."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many, 0 to 999."}},"additionalProperties":false},"description":"Extras to add; none by default."},"travellers":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 1 and 999","title":"between(1, 999)","minimum":1,"maximum":999}],"description":"For vehicle and group units: how many travellers ride, 1 to 999."},"currency":{"type":"string","description":"The ISO 4217 currency to price in, e.g. `EUR`: one the storefront offers, its display currency by default (USD in staff's preview).","pattern":"^[A-Z]{3}$"},"pickup":{"allOf":[{"$ref":"#/components/schemas/PickupRequest"}],"description":"Where to collect the travellers, for an option with a pickup plan."},"rooms":{"type":"array","items":{"type":"object","required":["room_type_id","qty"],"properties":{"room_type_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The room type, one of the option's `room_types`."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many rooms of the type, or beds in it when it is shared, 0 to 999."}},"additionalProperties":false},"description":"For an option with room types (its `room_types`): how the group sleeps, as rooms, or beds in a shared room, up to 10 entries. They must sleep every traveller who takes a seat; needed when the option has room types.","title":"maxItems(10)","maxItems":10},"lead":{"type":"object","required":["name","email","phone"],"properties":{"name":{"type":"string","description":"The lead traveller's name, up to 200 characters."},"email":{"type":"string","description":"Their email: the booking's confirmation and its signed link go to it."},"phone":{"type":"string","description":"Their phone or WhatsApp number with its country code, e.g. `+491512345678`.","pattern":"^\\+[1-9]\\d{6,14}$"}},"additionalProperties":false,"description":"The lead traveller: who the operator contacts about the booking."},"payment_method":{"type":"string","enum":["bank_transfer","pay_on_arrival","payment_link"],"description":"Needed when the storefront has payment methods: one it offers for the start."},"special_requests":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"Anything the operator should know, up to 2000 characters; null for nothing."},"locale":{"type":"string","description":"The traveller's language, e.g. `fr`: their emails use it when the storefront offers it, else the storefront's default.","pattern":"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$","title":"maxLength(35)","maxLength":35},"traveller_details":{"type":"array","items":{"$ref":"#/components/schemas/TravellerInput"},"description":"Each traveller's details, the lead first, when the option asks for them (its `traveller_details`). Needed in full when it asks at checkout; otherwise any given now are kept and the rest are due later.","title":"maxItems(100)","maxItems":100},"pickup_details":{"allOf":[{"$ref":"#/components/schemas/PickupDetails"}],"description":"Answers to what the pickup's rule asks (its `asks`), e.g. the flight: they complete or replace those sent with the hold or the pickup. Checkout refuses with `PickupDetailsRequired` while a `flight`, `ship` or `room` asked for is missing."},"attribution":{"anyOf":[{"$ref":"#/components/schemas/Attribution"},{"type":"null"}],"description":"The campaign and the page that sent the traveller (issue #532), as the site saw them on landing: they replace the hold's; null or left out keeps the hold's."}},"additionalProperties":false},"CheckedOutBooking":{"type":"object","required":["ref","status","start","pax","hold_expires_at","pending_expires_at","trip","payment","details","cancellation","rejection_reason","quote","booking_token","contact_token"],"properties":{"ref":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"status":{"type":"string","enum":["on_hold","pending","confirmed","completed","cancelled","rejected","expired"],"description":"Where the booking stands: `on_hold` while checkout holds its places, `pending` until the operator confirms it, `confirmed`, `completed` once the trip is over, or `cancelled`, `rejected` or `expired`."},"start":{"type":"string","description":"The start booked, e.g. `2026-10-07T08:00`, or the date alone for an all-day start."},"pax":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many travellers it is for."},"hold_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"While it is `on_hold`, when its places go back on sale; null otherwise."},"pending_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When a pending booking expires if the operator hasn't confirmed it."},"trip":{"anyOf":[{"type":"object","required":["title","option","product_id"],"properties":{"title":{"type":"string","description":"The trip's title as sold."},"option":{"type":"string","description":"The option's name as sold."},"product_id":{"type":"string","description":"The trip's id, which opens its page (`/v1/public/products/{id}`) while it is on sale."}},"additionalProperties":false},{"type":"null"}],"description":"The trip as sold; null before checkout."},"payment":{"allOf":[{"$ref":"#/components/schemas/BookingPayment"}],"description":"How it is paid: the method, its status, when it is due and where to send a transfer; null where the storefront tracks no payment."},"details":{"type":"object","required":["status","due_at","needed","complete","overdue"],"properties":{"status":{"type":"string","enum":["not_needed","missing","partial","complete"],"description":"`not_needed` when the trip asks the lead only; `missing` when no traveller is given; `partial` when some are; `complete` when every traveller is."},"due_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When details asked after booking are due; null when they're asked at checkout or not at all."},"needed":{"type":"integer","description":"Travellers whose details are needed.","title":"int"},"complete":{"type":"integer","description":"Travellers whose details are complete, e.g. 2 of 4.","title":"int"},"overdue":{"type":"boolean","description":"The deadline passed with details missing. The booking is unchanged: the operator decides."}},"additionalProperties":false,"description":"Its travellers' details: how many are complete, and when the rest are due."},"cancellation":{"anyOf":[{"type":"object","required":["by","at","refund_pct","refund","paid"],"properties":{"by":{"type":"string","enum":["traveller","operator"],"description":"Who cancelled it: the `traveller` or the `operator`."},"at":{"type":"string","description":"When it was cancelled, as an ISO 8601 timestamp in UTC."},"refund_pct":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The percent of what was paid that is refunded, from the policy as sold."},"refund":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What is refunded, in the booking's currency."},"paid":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What the traveller had paid upfront, in the booking's currency, less a balance a change left to collect and part refunds given before, which the refund is a share of: zero when nothing was paid (a transfer or link never paid, or paid on the day), so nothing is refunded."}},"additionalProperties":false},{"type":"null"}],"description":"For a cancelled booking: who cancelled it, when, and what is refunded of what was paid."},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"For a rejected booking: why, as the seller wrote it for the traveller; null otherwise."},"quote":{"allOf":[{"$ref":"#/components/schemas/Quote"}],"description":"The price as sold: its lines, totals and policy, kept as they were at checkout."},"booking_token":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The token from the booking's signed link, the same one its \"manage your booking\" email carries: send it as `token` to `/v1/public/bookings/{ref}`, its `/refund` preview and `/cancel`, so the confirmation page opens without the lead's email. Keep it as the email's link is kept: only on the traveller's side, never in an address. Null when the same hold is checked out again, since a repeat needs only the ref and the email, and an email alone never cancels."},"contact_token":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Proves this browser made the booking, for the self-service window (30 minutes by default): the confirmation screen sends it as `token` to `/v1/public/bookings/{ref}/contact-changes` to fix a mistyped email. Keep it only in this browser session. Null when the same hold is checked out again."}},"additionalProperties":false},"BookingPayment":{"anyOf":[{"type":"object","required":["method","status","due_at","on_arrival","bank","receipt","link"],"properties":{"method":{"anyOf":[{"type":"string","enum":["bank_transfer","pay_on_arrival","payment_link"],"description":"`bank_transfer` to the storefront's account, `pay_on_arrival` on the day, or a `payment_link` the operator sends by hand."},{"type":"string","enum":["cash","card","other"]}],"description":"One of the storefront's methods; or `cash`, `card` or `other` for money staff took when they made the booking (issue #515)."},"status":{"type":"string","enum":["unpaid","proof_submitted","part_paid","paid","pay_on_arrival","refunded"],"description":"`unpaid`; `proof_submitted` once a transfer receipt is uploaded; `part_paid`; `paid`; `pay_on_arrival`, paid on the day; or `refunded`."},"due_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When an unpaid booking expires."},"on_arrival":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"What is paid on the day, fixed at checkout."},"bank":{"anyOf":[{"$ref":"#/components/schemas/BankDetails"},{"type":"null"}],"description":"Where to send a bank transfer."},"receipt":{"anyOf":[{"type":"object","required":["content_type","bytes","uploaded_at"],"properties":{"content_type":{"type":"string","description":"The file's type: `application/pdf`, `image/jpeg`, `image/png` or `image/webp`."},"bytes":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The file's size, in bytes."},"uploaded_at":{"type":"string","description":"When it was uploaded, as an ISO 8601 timestamp in UTC."}},"additionalProperties":false},{"type":"null"}],"description":"The transfer receipt the traveller uploaded, if any."},"link":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"For a payment link, the seller's page to pay on: the storefront's own payment page, from checkout, else the link staff add to the booking for its amount (issue #540); null until then."}},"additionalProperties":false},{"type":"null"}],"description":"Null where the storefront tracks no payment."},"BankDetails":{"type":"object","required":["account_name","bank","iban"],"properties":{"account_name":{"type":"string","description":"The account holder's name, up to 140 characters."},"bank":{"type":"string","description":"The bank's name, up to 140 characters."},"iban":{"type":"string","description":"The IBAN, in capitals without spaces.","pattern":"^[A-Z]{2}\\d{2}[A-Z0-9]{8,30}$"},"swift":{"anyOf":[{"type":"string","description":"a string matching the pattern ^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$","pattern":"^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$"},{"type":"null"}],"description":"The bank's SWIFT (BIC) code, 8 or 11 characters; null when not given."},"note":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"A line for the traveller, e.g. what to put as the reference, up to 500 characters; null for none."}},"additionalProperties":false},"Booking":{"type":"object","required":["ref","status","start","pax","hold_expires_at","pending_expires_at","trip","payment","details","cancellation","rejection_reason","quote"],"properties":{"ref":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"status":{"type":"string","enum":["on_hold","pending","confirmed","completed","cancelled","rejected","expired"],"description":"Where the booking stands: `on_hold` while checkout holds its places, `pending` until the operator confirms it, `confirmed`, `completed` once the trip is over, or `cancelled`, `rejected` or `expired`."},"start":{"type":"string","description":"The start booked, e.g. `2026-10-07T08:00`, or the date alone for an all-day start."},"pax":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many travellers it is for."},"hold_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"While it is `on_hold`, when its places go back on sale; null otherwise."},"pending_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When a pending booking expires if the operator hasn't confirmed it."},"trip":{"anyOf":[{"type":"object","required":["title","option","product_id"],"properties":{"title":{"type":"string","description":"The trip's title as sold."},"option":{"type":"string","description":"The option's name as sold."},"product_id":{"type":"string","description":"The trip's id, which opens its page (`/v1/public/products/{id}`) while it is on sale."}},"additionalProperties":false},{"type":"null"}],"description":"The trip as sold; null before checkout."},"payment":{"allOf":[{"$ref":"#/components/schemas/BookingPayment"}],"description":"How it is paid: the method, its status, when it is due and where to send a transfer; null where the storefront tracks no payment."},"details":{"type":"object","required":["status","due_at","needed","complete","overdue"],"properties":{"status":{"type":"string","enum":["not_needed","missing","partial","complete"],"description":"`not_needed` when the trip asks the lead only; `missing` when no traveller is given; `partial` when some are; `complete` when every traveller is."},"due_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When details asked after booking are due; null when they're asked at checkout or not at all."},"needed":{"type":"integer","description":"Travellers whose details are needed.","title":"int"},"complete":{"type":"integer","description":"Travellers whose details are complete, e.g. 2 of 4.","title":"int"},"overdue":{"type":"boolean","description":"The deadline passed with details missing. The booking is unchanged: the operator decides."}},"additionalProperties":false,"description":"Its travellers' details: how many are complete, and when the rest are due."},"cancellation":{"anyOf":[{"type":"object","required":["by","at","refund_pct","refund","paid"],"properties":{"by":{"type":"string","enum":["traveller","operator"],"description":"Who cancelled it: the `traveller` or the `operator`."},"at":{"type":"string","description":"When it was cancelled, as an ISO 8601 timestamp in UTC."},"refund_pct":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The percent of what was paid that is refunded, from the policy as sold."},"refund":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What is refunded, in the booking's currency."},"paid":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What the traveller had paid upfront, in the booking's currency, less a balance a change left to collect and part refunds given before, which the refund is a share of: zero when nothing was paid (a transfer or link never paid, or paid on the day), so nothing is refunded."}},"additionalProperties":false},{"type":"null"}],"description":"For a cancelled booking: who cancelled it, when, and what is refunded of what was paid."},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"For a rejected booking: why, as the seller wrote it for the traveller; null otherwise."},"quote":{"allOf":[{"$ref":"#/components/schemas/Quote"}],"description":"The price as sold: its lines, totals and policy, kept as they were at checkout."}},"additionalProperties":false},"TravellerForm":{"type":"object","required":["setting","progress","pax","erase_after_days","travellers"],"properties":{"setting":{"type":"object","required":["scope"],"properties":{"scope":{"type":"string","enum":["lead","all"],"description":"`lead`: the person booking only; `all`: every traveller."},"fields":{"type":"array","items":{"$ref":"#/components/schemas/TravellerFieldRule"},"description":"With `all`, the fields asked, each once; `full_name` is always asked, of everyone. None by default, and none with `lead`.","title":"maxItems(9)","maxItems":9},"collect":{"type":"string","enum":["checkout","after_booking"],"description":"`checkout`: due at booking; `after_booking` (the default): due `due_hours_before` the start."},"due_hours_before":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 1 and 720","title":"between(1, 720)","minimum":1,"maximum":720}],"description":"For `after_booking`, how many hours before the start the details are due, 1 to 720; 48 by default."},"preset":{"anyOf":[{"type":"string","enum":["standard","full_names","border_crossing","overnight_camp","adventure"]},{"type":"null"}],"description":"The preset it started from, if any; edits keep it."}},"additionalProperties":false,"description":"What the trip asks: which fields, required or not, and for which unit types."},"progress":{"allOf":[{"$ref":"#/components/schemas/TravellerDetailsProgress"}],"description":"How far along the booking is, and when the rest are due."},"pax":{"type":"integer","description":"How many travellers the booking has: positions 1 (the lead) to pax.","title":"int"},"erase_after_days":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"Passport numbers, dates of birth and medical notes are erased this many days after the trip; tell travellers so."},"travellers":{"type":"array","items":{"$ref":"#/components/schemas/Traveller"},"description":"Those given so far, sensitive fields masked; with a traveller's own link, only theirs."}},"additionalProperties":false},"TravellerFieldRule":{"type":"object","required":["key"],"properties":{"key":{"type":"string","enum":["full_name","date_of_birth","nationality","passport","gender","weight_height","dietary","medical","room"],"description":"full_name; date_of_birth; nationality; passport (number and expiry); gender; weight_height (kg and cm); dietary; medical (notes for operations staff); room (hotel room, for pickups)."},"required":{"type":"boolean","description":"Whether it must be given; true by default."},"unit_types":{"type":"array","items":{"type":"string","enum":["adult","child","infant","senior","youth","student","group","vehicle","room","other"],"description":"What the unit counts: an age band (`adult`, `child`, `infant`, `senior`, `youth`, `student`), a `group`, a `vehicle`, a `room`, or `other`."},"description":"Ask only travellers of these unit types, e.g. adults; empty for everyone."}},"additionalProperties":false},"TravellerDetailsProgress":{"type":"object","required":["status","due_at","needed","complete","overdue"],"properties":{"status":{"type":"string","enum":["not_needed","missing","partial","complete"],"description":"`not_needed` when the trip asks the lead only; `missing` when no traveller is given; `partial` when some are; `complete` when every traveller is."},"due_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When details asked after booking are due; null when they're asked at checkout or not at all."},"needed":{"type":"integer","description":"Travellers whose details are needed.","title":"int"},"complete":{"type":"integer","description":"Travellers whose details are complete, e.g. 2 of 4.","title":"int"},"overdue":{"type":"boolean","description":"The deadline passed with details missing. The booking is unchanged: the operator decides."}},"additionalProperties":false},"Traveller":{"type":"object","required":["id","position","unit_type","first_name","last_name","date_of_birth","age","nationality","passport_number","passport_expiry","gender","weight_kg","height_cm","dietary","dietary_note","medical_note","room","filled_by","missing","masked","erased"],"properties":{"id":{"type":"string","description":"The traveller's id."},"position":{"type":"integer","description":"1 for the lead.","title":"int"},"unit_type":{"anyOf":[{"type":"string","enum":["adult","child","infant","senior","youth","student","group","vehicle","room","other"],"description":"What the unit counts: an age band (`adult`, `child`, `infant`, `senior`, `youth`, `student`), a `group`, a `vehicle`, a `room`, or `other`."},{"type":"null"}],"description":"Which of the booking's units they are, e.g. `child`; null when not given."},"first_name":{"type":"string","description":"Their first name, or their only one."},"last_name":{"type":"string","description":"Their last name; empty for someone with one name."},"date_of_birth":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Masked (`••••-••-••`) unless revealed; `age` says the age."},"age":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"On the trip's first day."},"nationality":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"An ISO 3166-1 alpha-2 code, e.g. `DE`; null when not given."},"passport_number":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Masked to its last four (`•••• 0T47`) unless revealed."},"passport_expiry":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the passport expires, `YYYY-MM-DD`; null when not given."},"gender":{"anyOf":[{"type":"string","enum":["female","male","other"]},{"type":"null"}],"description":"`female`, `male` or `other`; null when not given."},"weight_kg":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"Weight in kg; null when not given."},"height_cm":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"Height in cm; null when not given."},"dietary":{"type":"array","items":{"type":"string","enum":["none","vegetarian","vegan","halal","kosher","gluten_free","lactose_free","nut_allergy","other"]},"description":"What they eat or avoid, e.g. `vegetarian`; empty when nothing is said."},"dietary_note":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"More about what they eat; null for none."},"medical_note":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Masked unless revealed."},"room":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Their hotel room, for the pickup; null when not given."},"filled_by":{"type":"string","enum":["lead","traveller","staff"],"description":"Who filled them in last: the `lead`, the `traveller` through their own link, or `staff`."},"missing":{"type":"array","items":{"type":"string","enum":["full_name","date_of_birth","nationality","passport","gender","weight_height","dietary","medical","room"],"description":"full_name; date_of_birth; nationality; passport (number and expiry); gender; weight_height (kg and cm); dietary; medical (notes for operations staff); room (hotel room, for pickups)."},"description":"Required fields not given yet."},"masked":{"type":"boolean","description":"True while the sensitive fields are masked; false once revealed."},"erased":{"type":"boolean","description":"Sensitive details were erased after the trip."}},"additionalProperties":false},"TravellerSave":{"type":"object","required":["first_name"],"properties":{"unit_type":{"anyOf":[{"type":"string","enum":["adult","child","infant","senior","youth","student","group","vehicle","room","other"],"description":"What the unit counts: an age band (`adult`, `child`, `infant`, `senior`, `youth`, `student`), a `group`, a `vehicle`, a `room`, or `other`."},{"type":"null"}],"description":"Which of the booking's units this traveller is, e.g. child; asked fields can depend on it."},"first_name":{"type":"string","description":"Their first name, or their only one, up to 100 characters."},"last_name":{"type":"string","description":"Empty for someone with one name."},"date_of_birth":{"anyOf":[{"type":"string","pattern":"^$|^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"description":"Their date of birth, `YYYY-MM-DD`; sensitive. Masked when read. Left out or null, the saved value is kept; an empty string erases it; a masked value is refused."},"nationality":{"anyOf":[{"type":"string","description":"a string matching the pattern ^[A-Z]{2}$","pattern":"^[A-Z]{2}$"},{"type":"null"}],"description":"Their nationality, an ISO 3166-1 alpha-2 code in capitals, e.g. `DE`; null when not asked or not given."},"passport_number":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"Their passport number, 5 to 20 letters and digits; sensitive. Masked when read. Left out or null, the saved value is kept; an empty string erases it; a masked value is refused."},"passport_expiry":{"anyOf":[{"type":"string","description":"a string matching the pattern ^\\d{4}-\\d{2}-\\d{2}$","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"description":"When their passport expires, `YYYY-MM-DD`: after the trip ends. Null when not asked or not given."},"gender":{"anyOf":[{"type":"string","enum":["female","male","other"]},{"type":"null"}],"description":"`female`, `male` or `other`; null when not asked or not given."},"weight_kg":{"anyOf":[{"$ref":"#/components/schemas/Int","description":"a number between 3 and 300","title":"between(3, 300)","minimum":3,"maximum":300},{"type":"null"}],"description":"Their weight in kg, 3 to 300; null when not asked or not given."},"height_cm":{"anyOf":[{"$ref":"#/components/schemas/Int","description":"a number between 40 and 250","title":"between(40, 250)","minimum":40,"maximum":250},{"type":"null"}],"description":"Their height in cm, 40 to 250; null when not asked or not given."},"dietary":{"type":"array","items":{"type":"string","enum":["none","vegetarian","vegan","halal","kosher","gluten_free","lactose_free","nut_allergy","other"]},"description":"What they eat or avoid, each once, e.g. `vegetarian` or `nut_allergy`; none by default.","title":"maxItems(9)","maxItems":9},"dietary_note":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"More about what they eat, up to 300 characters; null for none."},"medical_note":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"What operations staff should know about their health, up to 1000 characters; sensitive. Masked when read. Left out or null, the saved value is kept; an empty string erases it; a masked value is refused."},"room":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"Their hotel room, for the pickup, up to 20 characters; null when not asked or not given."}},"additionalProperties":false},"TravellerLink":{"type":"object","required":["position","token","url"],"properties":{"position":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The traveller's place in the booking."},"token":{"type":"string","description":"Opens only this traveller's card, with `traveller` set to the position."},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The storefront's booking page for them, when it has one."}},"additionalProperties":false},"RefundPreview":{"type":"object","required":["days_before","refund_pct","refund","paid"],"properties":{"days_before":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"Whole days before the start, now."},"refund_pct":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The percent of what was paid that cancelling now refunds, from the policy as sold."},"refund":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What cancelling now refunds, in the booking's currency."},"paid":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","description":"Minor units of the currency, e.g. 12500 for USD 125.00 or 580595 for OMR 580.595.","title":"int"},"currency":{"type":"string","description":"The ISO 4217 currency code, e.g. `USD`, `EUR` or `OMR`."}},"additionalProperties":false,"description":"What was paid upfront, less a balance a change left to collect and part refunds already given, which the refund is a share of: nothing while a transfer is unpaid or for a booking paid on the day; local lines are never refunded."}},"additionalProperties":false},"PublicPaymentMethod":{"type":"object","required":["kind","deadline_hours","currencies"],"properties":{"kind":{"type":"string","enum":["bank_transfer","pay_on_arrival","payment_link"],"description":"`bank_transfer` to the storefront's account, `pay_on_arrival` on the day, or a `payment_link` the operator sends by hand."},"deadline_hours":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"Hours after checkout to pay; null for pay on arrival."},"currencies":{"type":"array","items":{"type":"string"},"description":"For pay on arrival: what is accepted on the day."}},"additionalProperties":false},"EnquiryInput":{"type":"object","required":["kind","name","email","message"],"properties":{"kind":{"type":"string","enum":["product","planner","contact"],"description":"`product`: a question about a trip; `planner`: a request to plan a trip; `contact`: a general message."},"name":{"type":"string","description":"The traveller's name, up to 200 characters."},"email":{"type":"string","description":"The traveller's email, where the answer goes."},"phone":{"anyOf":[{"type":"string","description":"a string matching the pattern ^\\+[1-9]\\d{6,14}$","pattern":"^\\+[1-9]\\d{6,14}$"},{"type":"null"}],"description":"Their phone or WhatsApp number with its country code, e.g. `+491512345678`; null for none."},"message":{"type":"string","description":"What they ask, up to 4000 characters."},"product_id":{"anyOf":[{"$ref":"#/components/schemas/UUID"},{"type":"null"}],"description":"The trip asked about, by id, for a `product` enquiry; null otherwise."},"date_from":{"anyOf":[{"type":"string","description":"a string matching the pattern ^\\d{4}-\\d{2}-\\d{2}$","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"description":"The first day they could travel, `YYYY-MM-DD`; null when not given."},"date_to":{"anyOf":[{"type":"string","description":"a string matching the pattern ^\\d{4}-\\d{2}-\\d{2}$","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"description":"The last day they could travel, `YYYY-MM-DD`; null when not given."},"travellers":{"anyOf":[{"type":"object","required":[],"properties":{"adults":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 99","title":"between(0, 99)","minimum":0,"maximum":99}],"description":"Adults, 0 to 99; 0 by default."},"children":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 99","title":"between(0, 99)","minimum":0,"maximum":99}],"description":"Children, 0 to 99; 0 by default."},"infants":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 99","title":"between(0, 99)","minimum":0,"maximum":99}],"description":"Infants, 0 to 99; 0 by default."}},"additionalProperties":false},{"type":"null"}],"description":"Who would travel; null when not given."},"special_needs":{"anyOf":[{"type":"string","description":"a string that will be trimmed"},{"type":"null"}],"description":"Access needs or other requests, up to 1000 characters; null for none."},"interests":{"type":"array","items":{"type":"string","description":"a string that will be trimmed"},"description":"What they'd like to do, e.g. `Desert`, up to 20; none by default.","title":"maxItems(20)","maxItems":20},"locale":{"type":"string","description":"The traveller's language, e.g. `fr`, for the auto-reply: when the storefront offers it, else the storefront's default.","pattern":"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$","title":"maxLength(35)","maxLength":35},"website":{"type":"string","description":"Leave it out or empty: a field people never see, so a filled one marks a bot, whose enquiry is accepted and dropped."}},"additionalProperties":false},"PublicOffer":{"type":"object","required":["id","state","start","units","travellers","includes","valid_until","booking_ref","option_id","ref","traveller_name","trip","option","seller","quote","refusal"],"properties":{"id":{"type":"string","description":"The offer's id."},"state":{"type":"string","enum":["open","expired","withdrawn","booked"],"description":"`open` until its last day, then `expired`; `withdrawn` by the seller; `booked` once its traveller checks it out."},"start":{"type":"string","description":"The start it is for, e.g. `2026-10-07` or `2026-10-07T08:00`."},"units":{"type":"array","items":{"type":"object","required":["unit_id","qty"],"properties":{"unit_id":{"type":"string","description":"One of the option's units."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many of it."}},"additionalProperties":false},"description":"The group it is for."},"travellers":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"For a group or vehicle unit: the travellers who ride."},"includes":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What the price includes, as the seller wrote it."},"valid_until":{"type":"string","description":"Its last day, in the trip's time zone."},"booking_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The booking made from it, once booked."},"option_id":{"type":"string","description":"The trip's option it is for."},"ref":{"type":"string","description":"The price request's reference, e.g. `EQ-7KQM2`, which its price line is named by."},"traveller_name":{"type":"string","description":"Who asked."},"trip":{"type":"object","required":["slug","title"],"properties":{"slug":{"type":"string","description":"The trip's slug, as its page's address takes it."},"title":{"type":"string","description":"The trip's title."}},"additionalProperties":false,"description":"The trip it is for."},"option":{"type":"string","description":"The option's name."},"seller":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"The seller's slug, as their page's address takes it."},"name":{"type":"string","description":"The seller's name."}},"additionalProperties":false,"description":"The seller who sent it."},"quote":{"anyOf":[{"$ref":"#/components/schemas/Quote"},{"type":"null"}],"description":"What it costs, from the quote engine: one line at the offer's price, the units' lines included, in the storefront's display currency unless `currency` names another it sells in. Null unless it is open and its start can be booked."},"refusal":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Why an open offer can't be booked now: a quote's refusal code (e.g. `SoldOut`, `PastCutoff`), or `NotSold` when its trip is off sale."}},"additionalProperties":false},"OfferHoldRequest":{"type":"object","required":[],"properties":{"currency":{"type":"string","description":"The ISO 4217 currency to price in, e.g. `EUR`: one the storefront offers, its display currency by default (USD in staff's preview).","pattern":"^[A-Z]{3}$"},"extras":{"type":"array","items":{"type":"object","required":["extra_id","qty"],"properties":{"extra_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The extra, one of the trip's."},"qty":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 999","title":"between(0, 999)","minimum":0,"maximum":999}],"description":"How many, 0 to 999."}},"additionalProperties":false},"description":"Extras to add; none by default."},"pickup":{"allOf":[{"$ref":"#/components/schemas/PickupRequest"}],"description":"Where to collect the travellers, for an option with a pickup plan."}},"additionalProperties":false,"description":"What the traveller may choose on the offer: the currency, extras and a pickup. Its start, group and price are the seller's."},"Calendar":{"type":"array","items":{"type":"object","required":["date","status","starts"],"properties":{"date":{"type":"string","description":"The day, `YYYY-MM-DD`, in the trip's time zone."},"status":{"type":"string","enum":["open","closed","sold_out","past_cutoff"],"description":"The day: `open` to book, `closed`, `sold_out`, or `past_cutoff` (too late to book). Open when any start is."},"starts":{"type":"array","items":{"type":"object","required":["time","status","remaining"],"properties":{"time":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`HH:MM`, or null when open all day."},"status":{"type":"string","enum":["open","closed","sold_out","past_cutoff"],"description":"This start: `open` to book, `closed`, `sold_out`, or `past_cutoff` (too late to book)."},"remaining":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"Places left; null when not counted."}},"additionalProperties":false},"description":"The day's start times, earliest first; empty when it doesn't run."}},"additionalProperties":false}},"PublicRedirect":{"type":"object","required":["to","code"],"properties":{"to":{"type":"string","description":"Where to send the visitor."},"code":{"type":"number","enum":[301,302],"description":"The status to answer with: 301 (moved for good) or 302 (for now)."}},"additionalProperties":false},"NumberFromString":{"type":"string","description":"a string to be decoded into a number"},"PlaceSuggestionList":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PlaceSuggestion"},"description":"Best match first: places in `city` first, then the whole name, its start, a word's start, anywhere; shorter names first."}},"additionalProperties":false},"PlaceSuggestion":{"type":"object","required":["place_id","name","kind","area","city","country","lat","lng","source"],"properties":{"place_id":{"type":"string","description":"Send it as `pickup.place_id`, or as `place_id` to the resolver."},"name":{"type":"string","description":"Its name in `locale` when translated, else English, e.g. `Muscat International Airport`."},"kind":{"type":"string","enum":["city","town","village","airport","harbour","nature","beach","wadi","desert","canyon","mountain","fjord","fort","palace","mosque","museum","market","landmark","ruins","park","attraction"],"description":"What it is, e.g. `airport` or `harbour`."},"area":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The locality, for telling similar names apart, e.g. `Seeb`."},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The city it is in, when the library knows it."},"country":{"type":"string","description":"ISO 3166-1 alpha-2, e.g. `OM`."},"lat":{"type":"number","description":"Its latitude, in decimal degrees."},"lng":{"type":"number","description":"Its longitude, in decimal degrees."},"source":{"type":"string","enum":["library"],"description":"The platform places library; street addresses from a geocoder come later."}},"additionalProperties":false},"PickupResolution":{"type":"object","required":["option_id","outcome","place","match_kind","rule_id","rule_name","distance_km","price","price_per","units","total","time_offset_min","pickup_window","confirmation","asks","reason","explanation"],"properties":{"option_id":{"type":"string","description":"The option the answer is for."},"outcome":{"type":"string","enum":["included","surcharge","on_request","not_offered"],"description":"The one answer: `included` = free; `surcharge` = at `price`; `on_request` = the operator confirms it and its price, and the booking goes ahead meanwhile; `not_offered` = no pickup there (a quote with it is refused)."},"place":{"allOf":[{"$ref":"#/components/schemas/PickupResolvedPlace"}],"description":"What the input was resolved to, with its map point."},"match_kind":{"anyOf":[{"type":"string","enum":["point","radius","area","everywhere_else"]},{"type":"null"}],"description":"Which kind of rule gave the answer; null when the option has no pickup."},"rule_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The rule that won; null for everywhere else."},"rule_name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The winning rule's name; null for everywhere else."},"distance_km":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Straight-line km, one decimal, from a winning radius's centre, else the plan's centre, e.g. `\"0.9\"`."},"price":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"Retail price per `price_per` in `currency`, as a quote's `pickup` line shows it; zero when included; null on request or not offered."},"price_per":{"anyOf":[{"type":"string","enum":["vehicle","person","booking"],"description":"What one price covers: a vehicle (private tours), a person or a booking."},{"type":"null"}],"description":"What `price` covers; null when free or without a price."},"units":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"How many times `price` is charged for `pax`: the pax per person, the vehicles per vehicle, 1 per booking; null without `pax` or a price."},"total":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"`price × units`; null without both."},"time_offset_min":{"anyOf":[{"$ref":"#/components/schemas/Int"},{"type":"null"}],"description":"Minutes before the start the pickup begins; null without a rule."},"pickup_window":{"anyOf":[{"$ref":"#/components/schemas/PickupWindow"},{"type":"null"}],"description":"For `start`: when to be ready; null without a start time, or on request."},"confirmation":{"type":"string","enum":["instant","operator"],"description":"`instant` = the pickup is set at booking; `operator` = the operator confirms the pickup (never the booking)."},"asks":{"type":"array","items":{"type":"string","enum":["flight","ship","room","note"],"description":"A detail asked only when the rule is chosen: `flight` (number and arrival time), `ship` (name and all-aboard time), `room` (room number) or `note` (free text)."},"description":"What will be asked, e.g. `[\"flight\"]`; checkout needs `flight`, `ship` and `room` when asked."},"reason":{"anyOf":[{"type":"string","enum":["outside_areas","option_has_no_pickup"]},{"type":"null"}],"description":"Why, when not offered: outside every area of the plan, or the option has no pickup (meeting point only, or no plan)."},"explanation":{"type":"string","description":"One plain sentence, in English for now, with the price as the traveller pays it, e.g. \"Ruwi Hotel is 0.9 km from the centre of 'Within 20 km of Ruwi', so pickup is free.\""}},"additionalProperties":false},"PickupResolvedPlace":{"type":"object","required":["place_id","name","kind","lat","lng"],"properties":{"place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The places library place; null for a pin."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its English name; null for a pin."},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The library place's kind, e.g. `airport`; null for a pin."},"lat":{"type":"number","description":"Latitude of the place or pin, in decimal degrees."},"lng":{"type":"number","description":"Longitude of the place or pin, in decimal degrees."}},"additionalProperties":false,"description":"What the input was resolved to, with its map point."},"RefundInsteadOffer":{"type":"object","required":["ref","reason","note","from","to","timezone","refund"],"properties":{"ref":{"type":"string","description":"The booking's reference, e.g. `TP-AB2C-DE3F`."},"reason":{"type":"string","enum":["weather","safety","too_few","other"],"description":"Why: `weather`, `safety`, `too_few` (too few travellers) or `other`."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What the operator added, as written."},"from":{"type":"string","description":"The start called off, in the trip's time zone."},"to":{"type":"string","description":"The start the booking was moved to: the offer stands until then."},"timezone":{"type":"string","description":"The trip's time zone, e.g. `Asia/Muscat`."},"refund":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","description":"Minor units of the currency, e.g. 12500 for USD 125.00 or 580595 for OMR 580.595.","title":"int"},"currency":{"type":"string","description":"The ISO 4217 currency code, e.g. `USD`, `EUR` or `OMR`."}},"additionalProperties":false,"description":"Everything paid and not yet refunded: what the refund gives back."}},"additionalProperties":false},"SignInLinkRequest":{"type":"object","required":["email","callback_url"],"properties":{"email":{"type":"string","description":"The traveller's email. A link goes to it whether or not it has bookings, so the answer never says which emails are known."},"callback_url":{"type":"string","description":"The page on the storefront's own site the link opens, e.g. `https://example.com/account/sign-in`; the link adds `token`. It must be on the same site as the storefront's `manage_booking_url`, so a link can never send a traveller elsewhere."},"locale":{"type":"string","description":"The traveller's language, e.g. `fr`: the email uses it when the storefront offers it, else the storefront's default.","pattern":"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$","title":"maxLength(35)","maxLength":35},"name":{"type":"string","description":"Their name, kept only when this email has no account yet; else the part of the email before `@`."}},"additionalProperties":false},"SignedIn":{"type":"object","required":["traveller","claimed"],"properties":{"traveller":{"allOf":[{"$ref":"#/components/schemas/SignedInTraveller"}],"description":"Who signed in."},"claimed":{"type":"array","items":{"type":"string"},"description":"The refs of guest bookings made with this email that became theirs now."}},"additionalProperties":false},"SignedInTraveller":{"type":"object","required":["id","email","name"],"properties":{"id":{"type":"string","description":"The traveller's id."},"email":{"type":"string","description":"The email they proved by signing in."},"name":{"type":"string","description":"Their name."}},"additionalProperties":false},"MyBookings":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MyBooking"},"description":"Newest first."}},"additionalProperties":false},"MyBooking":{"type":"object","required":["ref","status","start","pax","hold_expires_at","pending_expires_at","trip","payment","details","cancellation","rejection_reason","quote","storefront"],"properties":{"ref":{"type":"string","description":"The booking's ref, e.g. `TP-7KQM-2XWD`."},"status":{"type":"string","enum":["on_hold","pending","confirmed","completed","cancelled","rejected","expired"],"description":"Where the booking stands: `on_hold` while checkout holds its places, `pending` until the operator confirms it, `confirmed`, `completed` once the trip is over, or `cancelled`, `rejected` or `expired`."},"start":{"type":"string","description":"The start booked, e.g. `2026-10-07T08:00`, or the date alone for an all-day start."},"pax":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"How many travellers it is for."},"hold_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"While it is `on_hold`, when its places go back on sale; null otherwise."},"pending_expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When a pending booking expires if the operator hasn't confirmed it."},"trip":{"anyOf":[{"type":"object","required":["title","option","product_id"],"properties":{"title":{"type":"string","description":"The trip's title as sold."},"option":{"type":"string","description":"The option's name as sold."},"product_id":{"type":"string","description":"The trip's id, which opens its page (`/v1/public/products/{id}`) while it is on sale."}},"additionalProperties":false},{"type":"null"}],"description":"The trip as sold; null before checkout."},"payment":{"allOf":[{"$ref":"#/components/schemas/BookingPayment"}],"description":"How it is paid: the method, its status, when it is due and where to send a transfer; null where the storefront tracks no payment."},"details":{"type":"object","required":["status","due_at","needed","complete","overdue"],"properties":{"status":{"type":"string","enum":["not_needed","missing","partial","complete"],"description":"`not_needed` when the trip asks the lead only; `missing` when no traveller is given; `partial` when some are; `complete` when every traveller is."},"due_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When details asked after booking are due; null when they're asked at checkout or not at all."},"needed":{"type":"integer","description":"Travellers whose details are needed.","title":"int"},"complete":{"type":"integer","description":"Travellers whose details are complete, e.g. 2 of 4.","title":"int"},"overdue":{"type":"boolean","description":"The deadline passed with details missing. The booking is unchanged: the operator decides."}},"additionalProperties":false,"description":"Its travellers' details: how many are complete, and when the rest are due."},"cancellation":{"anyOf":[{"type":"object","required":["by","at","refund_pct","refund","paid"],"properties":{"by":{"type":"string","enum":["traveller","operator"],"description":"Who cancelled it: the `traveller` or the `operator`."},"at":{"type":"string","description":"When it was cancelled, as an ISO 8601 timestamp in UTC."},"refund_pct":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The percent of what was paid that is refunded, from the policy as sold."},"refund":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What is refunded, in the booking's currency."},"paid":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"What the traveller had paid upfront, in the booking's currency, less a balance a change left to collect and part refunds given before, which the refund is a share of: zero when nothing was paid (a transfer or link never paid, or paid on the day), so nothing is refunded."}},"additionalProperties":false},{"type":"null"}],"description":"For a cancelled booking: who cancelled it, when, and what is refunded of what was paid."},"rejection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"For a rejected booking: why, as the seller wrote it for the traveller; null otherwise."},"quote":{"allOf":[{"$ref":"#/components/schemas/Quote"}],"description":"The price as sold: its lines, totals and policy, kept as they were at checkout."},"storefront":{"type":"object","required":["id","name","kind"],"properties":{"id":{"type":"string","description":"The storefront's id."},"name":{"type":"string","description":"Its name, as the traveller knows it."},"kind":{"type":"string","enum":["direct","marketplace"],"description":"`direct`: one operator's own site; `marketplace`: the platform's, selling every operator's trips approved for the network."}},"additionalProperties":false,"description":"The storefront that sold it. It opens and cancels through that storefront's key."}},"additionalProperties":false},"MyPriceRequests":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MyPriceRequest"},"description":"Newest first; at most 50."}},"additionalProperties":false},"MyPriceRequest":{"type":"object","required":["id","reference","status","outcome","trip","date_from","date_to","travellers","special_needs","sent_at","offer"],"properties":{"id":{"type":"string","description":"The price request's id."},"reference":{"type":"string","description":"The reference the traveller was shown when they sent it, e.g. `EQ-3F2A1`."},"status":{"type":"string","enum":["open","answered","closed"],"description":"`open` until the seller answers, then `answered`, or `closed` with an `outcome`."},"outcome":{"anyOf":[{"type":"string","enum":["booked","declined","no_reply"],"description":"How it ended: the traveller `booked`, `declined`, or never replied (`no_reply`)."},{"type":"null"}],"description":"How a closed one ended: `booked`, `declined` or `no_reply`; null until it is closed."},"trip":{"type":"object","required":["id","slug","title","seller"],"properties":{"id":{"type":"string","description":"The trip's id."},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its slug, as in its page's address, which opens its page in place of the id; on a marketplace, with `seller.slug`. Null once it is no longer on sale on this storefront."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Its title in English; null when the trip no longer exists."},"seller":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"The operator's slug: on a marketplace, the `seller` that opens the trip's page by its slug."},"name":{"type":"string","description":"The operator's name, shown on the trip."}},"additionalProperties":false,"description":"Who sells it, and whom the request went to."}},"additionalProperties":false,"description":"The trip asked about, as the site links to its page."},"date_from":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The first day asked about, `YYYY-MM-DD`; null when the traveller left the dates to the seller."},"date_to":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The last day asked about, `YYYY-MM-DD`: the same day, or a month's last; null when the traveller left the dates to the seller."},"travellers":{"type":"object","required":[],"properties":{"adults":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 99","title":"between(0, 99)","minimum":0,"maximum":99}],"description":"Adults, 0 to 99; 0 by default."},"children":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 99","title":"between(0, 99)","minimum":0,"maximum":99}],"description":"Children, 0 to 99; 0 by default."},"infants":{"allOf":[{"$ref":"#/components/schemas/Int","description":"a number between 0 and 99","title":"between(0, 99)","minimum":0,"maximum":99}],"description":"Infants, 0 to 99; 0 by default."}},"additionalProperties":false,"description":"Who would travel; all 0 when the traveller didn't say."},"special_needs":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What the seller should plan for, as the traveller wrote it; null for none."},"sent_at":{"type":"string","description":"When it was sent, as an ISO 8601 timestamp in UTC."},"offer":{"anyOf":[{"type":"object","required":["id","token","state","valid_until","booking_ref"],"properties":{"id":{"type":"string","description":"The offer's id."},"token":{"type":"string","description":"Its link's token, as its email carries it: `/v1/public/offers/{id}` opens it with this, priced, to book."},"state":{"type":"string","enum":["open","expired","withdrawn","booked"],"description":"`open` to book until `valid_until`, then `expired`; `withdrawn` by the seller; `booked` once booked."},"valid_until":{"type":"string","description":"Its last day, `YYYY-MM-DD`, in the trip's time zone."},"booking_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The booking it became, e.g. `TP-7K4M-Q2XN`; null until it is booked."}},"additionalProperties":false},{"type":"null"}],"description":"The seller's price, once sent; null until then. Its amount is quoted on its own page, in the storefront's currency."}},"additionalProperties":false},"SavedTrips":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SavedTrip"},"description":"Newest first; only trips still on sale on this storefront."}},"additionalProperties":false},"SavedTrip":{"type":"object","required":["trip","saved_at"],"properties":{"trip":{"type":"object","required":["id","slug","title","seller"],"properties":{"id":{"type":"string","description":"The trip's id."},"slug":{"type":"string","description":"Its slug, as in its page's address, which opens its page in place of the id; on a marketplace, with `seller.slug`."},"title":{"type":"string","description":"Its title in English; the trip's page has it in the traveller's language."},"seller":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"The operator's slug, e.g. `vpo`: on a marketplace, the `seller` that opens the trip's page by its slug."},"name":{"type":"string","description":"The operator's name, shown on the trip."}},"additionalProperties":false,"description":"Who sells it; on a marketplace, its page takes `seller` with this slug."}},"additionalProperties":false,"description":"The trip saved, as the site links to its page."},"saved_at":{"type":"string","description":"When the traveller saved it, as an ISO 8601 timestamp in UTC."}},"additionalProperties":false},"SaveTripsRequest":{"type":"object","required":["trips"],"properties":{"trips":{"type":"array","items":{"type":"object","required":["trip"],"properties":{"trip":{"type":"string","description":"The trip's id, or its slug as in its page's address.","title":"maxLength(200)","minLength":1,"maxLength":200},"seller":{"type":"string","description":"On a marketplace, the seller's slug, as a trip's page takes it.","title":"maxLength(200)","minLength":1,"maxLength":200}},"additionalProperties":false},"description":"One trip, or a guest's whole list once they sign in, newest first; at most 200.","title":"maxItems(200)","minItems":1,"maxItems":200}},"additionalProperties":false},"PasskeyRequestOptions":{"type":"object","required":[],"properties":{},"additionalProperties":{"$id":"/schemas/unknown","title":"unknown"},"description":"WebAuthn's `PublicKeyCredentialRequestOptionsJSON`, for `PublicKeyCredential.parseRequestOptionsFromJSON` and `navigator.credentials.get()` (or `@simplewebauthn/browser`'s `startAuthentication`). No `allowCredentials`: the browser offers the traveller's passkeys for this site. Its `challenge` works once, for 5 minutes by default."},"PasskeyAssertion":{"type":"object","required":["id","rawId","type","response"],"properties":{"id":{"type":"string","description":"The credential's id, base64url.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(1400)","maxLength":1400},"rawId":{"type":"string","description":"The credential's id as raw bytes, base64url: the same as `id`.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(1400)","maxLength":1400},"type":{"type":"string","enum":["public-key"],"description":"Always `public-key`."},"response":{"type":"object","required":["clientDataJSON","authenticatorData","signature"],"properties":{"clientDataJSON":{"type":"string","description":"The client data the browser signed, base64url.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(10000)","maxLength":10000},"authenticatorData":{"type":"string","description":"The authenticator data, base64url.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(100000)","maxLength":100000},"signature":{"type":"string","description":"The signature over the authenticator data and the client data's hash, base64url.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(10000)","maxLength":10000},"userHandle":{"anyOf":[{"type":"string","description":"a string at most 200 character(s) long","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(200)","maxLength":200},{"type":"null"}],"description":"The user handle the passkey was made for, base64url; null or absent when the authenticator gives none."}},"additionalProperties":false,"description":"The authenticator's answer to `navigator.credentials.get()`."},"authenticatorAttachment":{"type":"string","enum":["platform","cross-platform"],"description":"`platform` for an authenticator built into the device; `cross-platform` for a security key or a phone."},"clientExtensionResults":{"type":"object","required":[],"properties":{},"additionalProperties":{"$id":"/schemas/unknown","title":"unknown"},"description":"The results of the WebAuthn extensions the browser ran; empty by default."}},"additionalProperties":false,"description":"The signed challenge, as the browser's `PublicKeyCredential.toJSON()` (or `@simplewebauthn/browser`'s `startAuthentication`) gives it: WebAuthn's own member names, so they are camelCase."},"PasskeyCreationOptions":{"type":"object","required":[],"properties":{},"additionalProperties":{"$id":"/schemas/unknown","title":"unknown"},"description":"WebAuthn's `PublicKeyCredentialCreationOptionsJSON`, for `PublicKeyCredential.parseCreationOptionsFromJSON` and `navigator.credentials.create()` (or `@simplewebauthn/browser`'s `startRegistration`). Its `challenge` works once, for 5 minutes by default."},"PasskeyRegistration":{"type":"object","required":["id","rawId","type","response"],"properties":{"id":{"type":"string","description":"The credential's id, base64url.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(1400)","maxLength":1400},"rawId":{"type":"string","description":"The credential's id as raw bytes, base64url: the same as `id`.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(1400)","maxLength":1400},"type":{"type":"string","enum":["public-key"],"description":"Always `public-key`."},"response":{"type":"object","required":["clientDataJSON","attestationObject"],"properties":{"clientDataJSON":{"type":"string","description":"The client data the browser signed, base64url.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(10000)","maxLength":10000},"attestationObject":{"type":"string","description":"The attestation object, base64url.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(100000)","maxLength":100000},"authenticatorData":{"type":"string","description":"The authenticator data, base64url, when the browser gives it.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(100000)","maxLength":100000},"transports":{"type":"array","items":{"type":"string","description":"a string at most 40 character(s) long","title":"maxLength(40)","maxLength":40},"description":"How the authenticator can be reached, e.g. `internal` or `hybrid`, up to 10.","title":"maxItems(10)","maxItems":10},"publicKeyAlgorithm":{"allOf":[{"$ref":"#/components/schemas/Int"}],"description":"The public key's COSE algorithm, e.g. `-7` for ES256."},"publicKey":{"type":"string","description":"The public key, base64url, when the browser gives it.","pattern":"^[A-Za-z0-9_-]*={0,2}$","title":"maxLength(10000)","maxLength":10000}},"additionalProperties":false,"description":"The authenticator's answer to `navigator.credentials.create()`."},"authenticatorAttachment":{"type":"string","enum":["platform","cross-platform"],"description":"`platform` for an authenticator built into the device; `cross-platform` for a security key or a phone."},"clientExtensionResults":{"type":"object","required":[],"properties":{},"additionalProperties":{"$id":"/schemas/unknown","title":"unknown"},"description":"The results of the WebAuthn extensions the browser ran; empty by default."},"name":{"type":"string","description":"What to call it in the traveller's list, e.g. `iPhone`; else `Passkey`. Not part of WebAuthn's JSON."}},"additionalProperties":false,"description":"The new credential, as the browser's `PublicKeyCredential.toJSON()` (or `@simplewebauthn/browser`'s `startRegistration`) gives it: WebAuthn's own member names, so they are camelCase. Plus an optional `name`."},"MyPasskeys":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Passkey"},"description":"Oldest first."}},"additionalProperties":false},"Passkey":{"type":"object","required":["id","name","rp_id","synced","created_at","last_used_at"],"properties":{"id":{"type":"string","description":"The passkey's id."},"name":{"type":"string","description":"What the traveller called it."},"rp_id":{"type":"string","description":"The site it signs in on: the host of that storefront's `manage_booking_url`."},"synced":{"type":"boolean","description":"Whether the authenticator backs it up, e.g. to the traveller's cloud keychain."},"created_at":{"type":"string","description":"When it was added, as an ISO 8601 timestamp in UTC."},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When it last signed in; null until it has."}},"additionalProperties":false},"ContactChangeCode":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"The six-digit code from the email."}},"additionalProperties":false},"ContactChange":{"type":"object","required":["id","field","status","value","code_sent_to","expires_at","verified_at"],"properties":{"id":{"type":"string","description":"The change's id, to send its code back to."},"field":{"type":"string","enum":["email","phone"],"description":"Which contact it changes: `email` or `phone`."},"status":{"type":"string","enum":["pending","verified","cancelled"],"description":"`pending` until the code comes back; `verified` once it has taken effect."},"value":{"type":"string","description":"The new contact, masked, e.g. `l••••h@gm••l.com` or `+49 ••• ••• 789`."},"code_sent_to":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where the code went, masked: the new email, or the booking's email for a new phone."},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When the code stops working."},"verified_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When it took effect; null until it has."}},"additionalProperties":false}},"securitySchemes":{"secretKey":{"description":"A secret API key, `tp_sk_…`, scoped and revocable.","type":"http","scheme":"bearer"},"session":{"description":"The session cookie set by `POST /v1/auth/sign-in/email`; over HTTPS it is named `__Secure-tp.session_token`.","type":"apiKey","name":"tp.session_token","in":"cookie"},"publishableKey":{"description":"A storefront's publishable key (a website key in the portal), `tp_pk_…`. It identifies the storefront, which fixes the operator, the channel, the languages and the currencies. Public scope only. From a server it always works; from a browser, only on the origins its storefront lists, else `403 OriginNotAllowed` (issue #525).","type":"apiKey","name":"Tp-Publishable-Key","in":"header"},"travellerSession":{"description":"The traveller's session cookie, set by `POST /v1/public/auth/verify`; over HTTPS it is named `__Secure-tp-traveller.session_token`. A storefront's server passes it on as it got it. A staff session is never accepted in its place.","type":"apiKey","name":"tp-traveller.session_token","in":"cookie"}}},"security":[],"tags":[{"name":"public","description":"What a storefront calls with its publishable key: its trips, places, pages and menus, quotes, holds, checkout and the traveller's booking."},{"name":"publicPickups","description":"Where a traveller is picked up: places suggested as they type, and the one answer for a place on an option."},{"name":"publicDepartures","description":"For a traveller whose departure was moved: what a full refund instead would be, and taking it."},{"name":"travellerSignIn","description":"Signing a traveller in with an emailed link, and signing them out."},{"name":"travellerMe","description":"The signed-in traveller, and their bookings across storefronts."},{"name":"travellerPasskeySignIn","description":"Signing a traveller in with a passkey (WebAuthn)."},{"name":"travellerPasskeys","description":"A signed-in traveller's passkeys: adding, listing and removing them."},{"name":"contactChanges","description":"A traveller fixing their booking's email or phone, proven with an emailed code."}],"servers":[{"url":"https://api.stg.vacationpackagesoman.com","description":"Staging"}]}