{"openapi":"3.1.0","info":{"title":"Wego API","description":"Wego's travel API: places, flights, hotels and fares. Please see https://docs.wego.com for more details.","version":"0.19.0"},"tags":[{"name":"Places","description":"Turn free text into typed travel locations. `getPlaces` resolves a city, airport, district or hotel name to results carrying the codes the flight and hotel searches take as input. `getNearbyPlaces` answers the follow-up – which other airports serve the same trip – from a place code or a coordinate pair."},{"name":"Countries","description":"Country-keyed reference data an agent can call before any search, and combine. `getCountryHolidays` returns a market's public holidays for spotting long weekends; `getVisaFreeDestinations` returns where a passport travels without a visa, as one complete list. Both take an ISO 3166-1 alpha-2 code as their only path key, which is what groups them under one tag – their results combine (visa-free supplies the destinations, holidays the free dates), rather than intersecting on a shared row field, since a holiday row carries no country code of its own. The visa list carries no visa type and no permitted stay – an absent country means absent from Wego's list, not that a visa is required."},{"name":"Flights","description":"The flight funnel: `createFlightSearch` starts an async search, `getFlightSearchResults` reads ranked snapshots while providers answer, `getFlightTrip` opens one trip with every fare. Fares with `kind: \"wego\"` continue into `getFareOptions` and `getFareBookingLink`; airline and partner fares carry their own handoff URL instead. Ids expire; a `404` means search again. `getFlightSchedules` sits outside the funnel – a published timetable with no prices and no search to settle."},{"name":"Hotels","description":"The hotel funnel, same shape as flights: `createHotelSearch`, `getHotelSearchResults` for ranked hotels, `getHotel` for static detail, `getHotelRates` for bookable rooms and rates (cheapest first, with board and refundability), `getHotelRateBookingLink` for the wego.com checkout URL."},{"name":"User","description":"The authenticated caller. `getCurrentUser` returns the identity behind the bearer token (the CLI's `wego whoami`)."},{"name":"Feedback","description":"Send feedback about the Wego CLI/API experience. `submitFeedback` records an optional category plus at least one of a rating (1-5) or a free-text message (the CLI's `wego feedback`); a category alone is rejected."}],"servers":[{"url":"https://api.wego.com"}],"security":[{"oauth2":[]},{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Wego auth server access token, sent as `Authorization: Bearer ` (RFC 6750)."},"oauth2":{"type":"oauth2","description":"OAuth2 authorization-code flow (PKCE supported) against the Wego auth server.","flows":{"authorizationCode":{"authorizationUrl":"https://auth.wego.com/user-auth/v2/users/oauth/authorize","tokenUrl":"https://auth.wego.com/user-auth/v2/users/oauth/token","x-scalar-client-id":"251815b9647317f4895122fd4924b7d44541d8fcc27be528435e3ad9bbf7e1ee","x-usePkce":"SHA-256","scopes":{"openid":"OpenID Connect sign-in.","profile":"Basic profile claims.","users":"User identity for the API."}}},"x-default-scopes":["openid","profile","users"]}},"schemas":{"Problem":{"type":"object","description":"RFC 9457 Problem Details, served as application/problem+json.","required":["type","title","status","instance","code","trace_id"],"properties":{"type":{"type":"string","format":"uri","description":"Problem-type URI. `about:blank` for now (no semantics beyond the status); real type URIs follow once the public host is fixed."},"title":{"type":"string","description":"Fixed human summary, the same across a `code`."},"status":{"type":"integer","description":"The HTTP status code, repeated as a JSON number."},"detail":{"type":"string","description":"Instance-specific human explanation of this failure."},"instance":{"type":"string","description":"The request path this occurrence happened on."},"code":{"type":"string","enum":["validation_failed","invalid_token","insufficient_scope","not_found","rates_require_hotel_search","rate_limited","bad_gateway","upstream_unavailable","upstream_rate_limited","internal_error"],"description":"Stable machine token from a closed enum – the field an agent branches on."},"trace_id":{"type":"string","description":"Correlates this response to its logs; also returned in the `x-trace-id` response header."}}}}},"paths":{"/health":{"get":{"operationId":"getHealth","summary":"Health check","description":"Liveness probe.","security":[],"responses":{"200":{"description":"The API is up.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","const":"healthy","description":"Always \"healthy\" when the service is answering."},"commit":{"description":"Git commit SHA of the running build; absent when the platform supplied none.","type":"string"}},"required":["status"]}}}}}}},"/v1/user":{"get":{"operationId":"getCurrentUser","tags":["User"],"summary":"Get the authenticated user","description":"Returns the caller's own identity, read from the verified access-token claims. Profile fields (email, name, country) appear only when the token carries them.","responses":{"200":{"description":"The caller's identity claims.","content":{"application/json":{"schema":{"type":"object","properties":{"sub":{"type":"string","description":"The token subject (the JWT `sub`): the user's email, which auth.wego.com uses as the stable user identifier."},"scope":{"description":"Space-separated OAuth scopes granted to the token.","type":"string"},"email":{"description":"The user's email address, when the token carries it.","type":"string"},"name":{"description":"The user's full display name, when present.","type":"string"},"first_name":{"description":"The user's given name, when present.","type":"string"},"last_name":{"description":"The user's family name, when present.","type":"string"},"country_code":{"description":"The user's market country code, when present (id_token-sourced; usually absent on the access token).","type":"string"},"uid":{"description":"The AS's own numeric user id. Published as `string | number` because that is what it is: it arrives as a number today, and a `string`-only declaration would be a promise the API does not keep.","anyOf":[{"type":"string"},{"type":"number"}]},"principal_name":{"description":"The auth server's principal name for the user, when present.","type":"string"}},"required":["sub"]}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/feedback":{"post":{"operationId":"submitFeedback","tags":["Feedback"],"summary":"Submit feedback","description":"Records feedback about the Wego CLI/API experience – a rating (1-5), a category, and/or a free-text message. At least one of rating or message is required. Fire-and-forget: returns 202 and never blocks on recording.","responses":{"202":{"description":"Feedback accepted.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","const":"received","description":"The feedback was accepted."}},"required":["status"]}}}},"400":{"description":"Invalid feedback body (e.g. neither rating nor message provided).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rating":{"example":5,"description":"Overall rating, 1 (poor) to 5 (great).","type":"integer","minimum":1,"maximum":5},"category":{"description":"Which area the feedback is about.","type":"string","enum":["flights","hotels","other"]},"message":{"example":"Fare options were exactly what I needed.","description":"Free-text feedback (bugs, ideas, what worked).","type":"string","minLength":1,"maxLength":2000},"version":{"description":"CLI version the feedback came from.","type":"string","maxLength":50}},"additionalProperties":false}}}}}},"/v1/places/nearby":{"get":{"operationId":"getNearbyPlaces","tags":["Places"],"summary":"Airports and cities near a point","description":"The airports (and optionally cities) closest to a place or a coordinate pair, nearest first – for finding an alternative departure airport serving the same trip. Pass either place (a code, resolved to coordinates here) or latitude+longitude, never neither.","responses":{"200":{"description":"Nearby places, nearest first, plus the origin they were measured from.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"id":{"description":"Opaque identifier, unique per place. Do not send it to other endpoints or build wego.com URLs from it: reference a place by code, or by cityCode for a hotels search.","anyOf":[{"type":"number"},{"type":"string"}]},"code":{"description":"IATA-style code (airport/city), when the place has one.","type":"string"},"name":{"type":"string","description":"Display name of the place."},"type":{"type":"string","description":"Place kind: city, airport, state, district or hotel."},"cityCode":{"description":"Code of the city this place belongs to.","type":"string"},"latitude":{"description":"Latitude in decimal degrees, when known.","type":"number"},"longitude":{"description":"Longitude in decimal degrees, when known.","type":"number"}},"required":["name","type"]},"description":"The nearby places found around the origin."},"metadata":{"type":"object","properties":{"resultCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Number of results on the current page (always <= pageSize)."},"totalCandidates":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Rows the upstream returned before this page was sliced. The upstream ignores per_page and answers with roughly ten rows, so pageSize can only narrow this, never reach further."},"hasMore":{"type":"boolean","description":"True when pageSize clipped the upstream rows."},"origin":{"type":"object","properties":{"latitude":{"type":"number","description":"Latitude the upstream was queried with."},"longitude":{"type":"number","description":"Longitude the upstream was queried with."},"resolvedFrom":{"type":"string","enum":["place","coordinates"],"description":"`place` when a code was resolved to these coordinates, `coordinates` when the caller supplied them."},"code":{"description":"The resolved place code, present only when resolvedFrom is place.","type":"string"},"name":{"description":"The resolved place name, present only when resolvedFrom is place.","type":"string"}},"required":["latitude","longitude","resolvedFrom"],"description":"The point the nearby search ran from, and how it was derived."}},"required":["resultCount","totalCandidates","hasMore","origin"],"description":"Pagination and the resolved origin for this nearby search."}},"required":["results","metadata"]}}}},"400":{"description":"Neither a place nor a coordinate pair, an out-of-range coordinate, or a place code that resolves to nothing with coordinates.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream places service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The places service is temporarily unavailable; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"query","name":"place","schema":{"type":"string","minLength":1,"maxLength":100},"description":"Place code to search around (e.g. LON, LHR). Resolved to coordinates before the upstream call."},{"in":"query","name":"latitude","schema":{"type":"number","minimum":-90,"maximum":90},"description":"Latitude of the point to search around, decimal degrees (-90 to 90). Give latitude and longitude together; use either this pair or place, never both."},{"in":"query","name":"longitude","schema":{"type":"number","minimum":-180,"maximum":180},"description":"Longitude of the point to search around, decimal degrees (-180 to 180). Give latitude and longitude together; use either this pair or place, never both."},{"in":"query","name":"types","schema":{"minItems":1,"type":"array","items":{"type":"string","enum":["city","airport","state","district","hotel"]}},"description":"Place types to resolve; repeat or comma-separate to mix."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35},"description":"Language tag for localized place names (e.g. en, ar). Defaults to en."},{"in":"query","name":"pageSize","schema":{"default":50,"type":"integer","minimum":1,"maximum":50},"description":"Max results (1-50). Defaults to 50, the maximum, because this read wants the complete set of nearby places, not a page of it. The upstream returns roughly ten rows and ignores paging, so pageSize can only narrow that set, never reach further."}]}},"/v1/places":{"get":{"operationId":"getPlaces","tags":["Places"],"summary":"Resolve travel locations","description":"Resolves a free-text location query to canonical Wego places (cities, airports, states, districts, hotels) with codes and coordinates, for use in later flight and hotel searches. When metadata.hasAmbiguity is true, clarify with the user before proceeding.","responses":{"200":{"description":"Matching places plus pagination/ambiguity metadata.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"id":{"description":"Opaque identifier, unique per place. Do not send it to other endpoints or build wego.com URLs from it: reference a place by code, or by cityCode for a hotels search.","anyOf":[{"type":"number"},{"type":"string"}]},"code":{"description":"IATA-style code (airport/city), when the place has one.","type":"string"},"name":{"type":"string","description":"Display name of the place."},"type":{"type":"string","description":"Place kind: city, airport, state, district or hotel."},"cityCode":{"description":"Code of the city this place belongs to.","type":"string"},"latitude":{"description":"Latitude in decimal degrees, when known.","type":"number"},"longitude":{"description":"Longitude in decimal degrees, when known.","type":"number"}},"required":["name","type"]},"description":"The matched places for this page."},"metadata":{"type":"object","properties":{"resultCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Number of results on the current page (always <= pageSize)."},"totalCandidates":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Total matches held for this query (post-dedup, pre-pagination) – the ceiling pagination can reach. 0 means no matches; an empty deep page with totalCandidates > 0 just means the offset is past the end."},"hasMore":{"type":"boolean","description":"True when a further page exists."},"hasAmbiguity":{"type":"boolean","description":"True when several distinct real-world locations share the query; ask the user to disambiguate before searching."},"disambiguationHint":{"description":"Short clarification sample, present only when hasAmbiguity.","type":"string"}},"required":["resultCount","totalCandidates","hasMore","hasAmbiguity"],"description":"Pagination and ambiguity signals for this place search."}},"required":["results","metadata"]}}}},"400":{"description":"Invalid query parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream places service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The places service is temporarily unavailable; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"query","name":"query","schema":{"type":"string","minLength":1,"maxLength":100,"example":"dubai"},"required":true,"description":"Free text to resolve to typed places with codes. A place-name search (city, airport, district, hotel)."},{"in":"query","name":"types","schema":{"minItems":1,"type":"array","items":{"type":"string","enum":["city","airport","state","district","hotel"]}},"description":"Place types to resolve; repeat or comma-separate to mix."},{"in":"query","name":"locale","schema":{"type":"string","minLength":1,"maxLength":35},"description":"Language tag for localized place names (e.g. en, ar). Omitted, the search is language-neutral: the query matches names in any language (sent upstream as the locale wildcard) and results carry canonical English names. Pass a tag to localize the returned names instead. getNearbyPlaces, by contrast, defaults to en."},{"in":"query","name":"page","schema":{"default":1,"type":"integer","minimum":1,"maximum":100},"description":"Page number, 1-based (max 100). Defaults to 1."},{"in":"query","name":"pageSize","schema":{"default":10,"type":"integer","minimum":1,"maximum":50},"description":"Results per page (1-50). Defaults to 10."}]}},"/v1/countries/{countryCode}/holidays":{"get":{"operationId":"getCountryHolidays","tags":["Countries"],"summary":"Public holidays for a market","description":"Public holidays in one Wego market over a date range, for spotting long weekends before searching flights. Give both fromDate and toDate, or neither – omit both and the API searches the next 90 days and says so in metadata.window/from/to.","responses":{"200":{"description":"Holidays in the searched range, plus that range.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Localized holiday name."},"key":{"type":"string","description":"Stable upstream slug (e.g. national_day) – the same holiday carries the same key across locales."},"startDate":{"type":"string","description":"Inclusive ISO YYYY-MM-DD start."},"endDate":{"type":"string","description":"Inclusive ISO YYYY-MM-DD end; equals startDate for one-day holidays."}},"required":["name","key","startDate","endDate"]},"description":"Public holidays in the resolved window."},"metadata":{"type":"object","properties":{"resultCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Number of holidays returned."},"countryCode":{"type":"string","description":"The market the holidays are for."},"window":{"type":"string","enum":["explicit","upcoming"],"description":"`explicit` when the caller supplied both dates, `upcoming` when the API chose the range (stated in from/to)."},"from":{"type":"string","description":"Inclusive ISO start of the range actually searched."},"to":{"type":"string","description":"Inclusive ISO end of the range actually searched."}},"required":["resultCount","countryCode","window","from","to"],"description":"The market, the resolved date window, and the result count for this read."}},"required":["results","metadata"]}}}},"400":{"description":"Unknown market, malformed date, only one of fromDate/toDate, or fromDate after toDate.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream holidays service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The holidays service is temporarily unavailable; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"countryCode","schema":{"type":"string","pattern":"^[A-Z]{2}$","example":"AE"},"required":true,"description":"ISO 3166-1 alpha-2 code of the Wego market whose public holidays you want (e.g. AE). A destination MARKET, not a passport: it must be one of Wego's markets, and an unknown one is rejected 400. The same path segment means a passport on the visa-free route."},{"in":"query","name":"fromDate","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"description":"Inclusive ISO start date. Give both fromDate and toDate, or neither."},{"in":"query","name":"toDate","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"description":"Inclusive ISO end date. Give both fromDate and toDate, or neither."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35},"description":"Locale for localized names (e.g. en, ar). Defaults to en."}]}},"/v1/countries/{countryCode}/visa-free-destinations":{"get":{"operationId":"getVisaFreeDestinations","tags":["Countries"],"summary":"Visa-free destinations for a passport","description":"Where a passport can travel without a visa, as one complete list (the API walks the upstream's pages). An inspiration list, not a visa rule: it carries no visa type and no permitted stay, and a country's absence means absent from Wego's list, never that a visa is required.","responses":{"200":{"description":"Visa-free destinations for this passport, keyed on countryCode for joining.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"countryCode":{"type":"string","description":"ISO 3166-1 alpha-2 code – the key to join this list on."},"name":{"type":"string","description":"Localized country name."},"keyCityCode":{"description":"The country's principal city code, ready for a flight search.","type":"string"},"currencyCode":{"description":"The destination's ISO 4217 currency code, when known.","type":"string"},"latitude":{"description":"Latitude of the destination's principal city, when known.","type":"number"},"longitude":{"description":"Longitude of the destination's principal city, when known.","type":"number"}},"required":["countryCode","name"]},"description":"Visa-free destinations for the passport."},"metadata":{"type":"object","properties":{"resultCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Number of destinations on the current page."},"totalCandidates":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Destinations assembled across every upstream page, pre-pagination. 0 means Wego lists none for this passport – NOT that a visa is required."},"hasMore":{"type":"boolean","description":"True when a further page exists."},"passportCountryCode":{"type":"string","description":"The passport the list is for."},"upstreamPagesFetched":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many upstream pages were read to assemble this list."},"coverage":{"type":"string","enum":["complete","truncated"],"description":"`complete` when the walk ended on a short upstream page. `truncated` when the page cap stopped it on a full page, so totalCandidates is a FLOOR and destinations may exist that this response does not carry. At exactly the cap (200) a complete list also reports `truncated`, since telling the two apart would cost another upstream page."}},"required":["resultCount","totalCandidates","hasMore","passportCountryCode","upstreamPagesFetched","coverage"],"description":"The passport, the walk's coverage, and the counts for this read."}},"required":["results","metadata"]}}}},"400":{"description":"Malformed passport country code, page or pageSize.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream destinations service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The destinations service is temporarily unavailable; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"countryCode","schema":{"type":"string","pattern":"^[A-Z]{2}$","example":"AE"},"required":true,"description":"ISO 3166-1 alpha-2 code of the PASSPORT whose visa-free destinations you want (e.g. AE). A passport, not a market: any well-formed code is accepted (a passport need not be a Wego market) and an unrecognized one returns an honest empty list. The same path segment means a market on the holidays route."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35},"description":"Locale for localized names (e.g. en, ar). Defaults to en."},{"in":"query","name":"page","schema":{"default":1,"type":"integer","minimum":1,"maximum":20},"description":"Page number, 1-based (max 20). Defaults to 1. The list is a bounded registry the API assembles whole, so paging is rarely needed and the cap is low by design."},{"in":"query","name":"pageSize","schema":{"default":200,"type":"integer","minimum":1,"maximum":200},"description":"Rows per page (1-200). Defaults to 200, which is also the maximum: this is a bounded registry the API walks completely, so the default returns the whole list. pageSize exists only to narrow a long answer, never to force paging."}]}},"/v1/flights/searches":{"post":{"operationId":"createFlightSearch","tags":["Flights"],"summary":"Create a flight search","description":"Creates a metasearch for the given route/dates/passengers and returns its searchId. Results accrue asynchronously – poll getFlightSearchResults with the returned searchId to read ranked trips.","responses":{"201":{"description":"Search created.","content":{"application/json":{"schema":{"type":"object","properties":{"searchId":{"type":"string","description":"The id of the created search; pass it to the results and trip reads."},"siteCode":{"type":"string","description":"The site code (Wego market) the search was created for."},"siteCodeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved siteCode: explicit (caller-supplied – including a market a client derived and passed) or default (US, no site supplied)."}},"required":["searchId","siteCode","siteCodeSource"],"description":"The created search id and the market it was created for."}}}},"400":{"description":"Invalid request body/query/path parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream flights service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The flights service is temporarily unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","pattern":"^[A-Z]{3}$","example":"DXB","description":"Origin airport or city IATA code, e.g. DXB."},"to":{"type":"string","pattern":"^[A-Z]{3}$","example":"LHR","description":"Destination airport or city IATA code, e.g. LHR."},"fromDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Outbound departure date, YYYY-MM-DD. Not in the past, within a year."},"toDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Return date, YYYY-MM-DD. Omit for a one-way search."},"cabin":{"default":"economy","description":"Cabin class requested for all passengers.","type":"string","enum":["economy","premium_economy","business","first"]},"adults":{"default":1,"description":"Adult passengers (1-9). Defaults to 1. Note the hotel search defaults adults to 2, since a room sleeps two.","type":"integer","minimum":1,"maximum":9},"children":{"default":0,"description":"Child passengers (0-8). Defaults to 0.","type":"integer","minimum":0,"maximum":8},"infants":{"default":0,"description":"Infant passengers (0-8). Must not exceed adults. Defaults to 0.","type":"integer","minimum":0,"maximum":8},"currency":{"default":"USD","description":"Pricing currency as a 3-letter ISO 4217 code. Defaults to USD.","type":"string","pattern":"^[A-Z]{3}$"},"locale":{"default":"en","description":"Response language tag (e.g. en, ar). Defaults to en.","type":"string","minLength":1,"maxLength":35},"siteCode":{"type":"string","pattern":"^[A-Z]{2}$","description":"Wego market (point of sale) as a 2-letter code, e.g. AE. Optional: if omitted the API defaults to US. A client that knows the user's market (the wego CLI derives it from the id_token) passes it as an explicit siteCode; the response always reports the siteCode used."}},"required":["from","to","fromDate"],"additionalProperties":false}}}}}},"/v1/flights/schedules":{"get":{"operationId":"getFlightSchedules","tags":["Flights"],"summary":"Published timetable for a route","description":"What actually flies a route – times, duration, aircraft, and the weekdays each flight runs – with no prices and no search to settle. Nonstop flights only. Airport codes resolve to their parent city (LHR to LON), and metadata echoes what each side resolved to.","responses":{"200":{"description":"Scheduled flights, plus the resolved route and the market used.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"airlineCode":{"type":"string","description":"Marketing carrier code – what `airline` filters on."},"departureAirportCode":{"type":"string","description":"Departure airport IATA code."},"arrivalAirportCode":{"type":"string","description":"Arrival airport IATA code."},"departureTime":{"type":"string","description":"Local HH:MM at the departure airport."},"arrivalTime":{"type":"string","description":"Local HH:MM at the arrival airport."},"durationMinutes":{"type":"number","description":"Total scheduled duration in minutes."},"stopsCount":{"type":"number","description":"Stops on the route, as the timetable reports them; 0 is nonstop. This read covers nonstop scheduled flights, so a connecting itinerary is absent rather than listed with a stop."},"arrivalDayOffset":{"type":"number","description":"Days the arrival falls after departure; 1 means next-day."},"segments":{"type":"array","items":{"type":"object","properties":{"departureAirportCode":{"type":"string","description":"Departure airport IATA code."},"arrivalAirportCode":{"type":"string","description":"Arrival airport IATA code."},"departureTime":{"type":"string","description":"Local HH:MM at the departure airport."},"arrivalTime":{"type":"string","description":"Local HH:MM at the arrival airport."},"airlineCode":{"type":"string","description":"Marketing carrier IATA code."},"airlineName":{"description":"Marketing carrier display name, when resolved.","type":"string"},"durationMinutes":{"description":"Segment duration in minutes, when reported.","type":"number"},"flightNumber":{"description":"The marketed designator, e.g. `TR 610`.","type":"string"},"aircraftCode":{"description":"Aircraft type code, when reported.","type":"string"},"aircraftName":{"description":"Aircraft type name, when reported.","type":"string"}},"required":["departureAirportCode","arrivalAirportCode","departureTime","arrivalTime","airlineCode"]},"description":"The individual flights that make up this schedule."},"operatingPeriods":{"type":"array","items":{"type":"object","properties":{"weekdays":{"description":"Days of the week the flight operates, 1 Monday to 7 Sunday. Absent means the upstream published no recurrence for this period.","type":"array","items":{"type":"integer","minimum":1,"maximum":7}},"startDate":{"description":"First date this recurrence is published for, as YYYY-MM-DD.","type":"string"},"endDate":{"description":"Last date this recurrence is published for, as YYYY-MM-DD.","type":"string"}},"description":"A published operating period: which weekdays the flight runs, and the date window that applies to."},"description":"When this flight runs – one entry per published operating period. Empty when the upstream states no recurrence, which means unknown, never daily."},"flightNumber":{"description":"The marketed designator, present on a single-segment schedule.","type":"string"},"aircraftCode":{"description":"Aircraft type code, when reported.","type":"string"}},"required":["airlineCode","departureAirportCode","arrivalAirportCode","departureTime","arrivalTime","durationMinutes","stopsCount","arrivalDayOffset","segments","operatingPeriods"]},"description":"The scheduled flights for this route, a timetable with no prices."},"metadata":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"The 1-based page returned."},"pageSize":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Rows requested per page."},"resultCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Scheduled flights on this page (always <= pageSize)."},"totalCandidates":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Scheduled flights the upstream held for this route, pre-pagination – the ceiling paging can reach. 0 means the upstream publishes no timetable for this route, NOT that nothing flies it."},"hasMore":{"type":"boolean","description":"True when a further page exists."},"coverage":{"type":"string","enum":["complete","truncated"],"description":"complete when the upstream returned its whole set for this route, truncated when it filled the API's upstream ceiling and may hold more. While truncated, read totalCandidates as a floor rather than a total."},"from":{"type":"object","properties":{"requested":{"type":"string","description":"Exactly what the caller sent, uppercased."},"resolvedCityCode":{"type":"string","description":"The city code sent upstream – LHR resolves to LON."}},"required":["requested","resolvedCityCode"],"description":"Departure route endpoint: what the caller sent and the city code it resolved to."},"to":{"type":"object","properties":{"requested":{"type":"string","description":"Exactly what the caller sent, uppercased."},"resolvedCityCode":{"type":"string","description":"The city code sent upstream – LHR resolves to LON."}},"required":["requested","resolvedCityCode"],"description":"Arrival route endpoint: what the caller sent and the city code it resolved to."},"siteCode":{"type":"string","description":"The market this request resolved to, as a 2-letter code. Echoed for consistency with the priced reads – a published timetable does not vary by market, so it does not change these rows."},"siteCodeSource":{"type":"string","enum":["explicit","default"],"description":"explicit when the caller supplied siteCode, default when the API applied the US floor."}},"required":["page","pageSize","resultCount","totalCandidates","hasMore","coverage","from","to","siteCode","siteCodeSource"],"description":"The page returned, how much the upstream held, the resolved route endpoints, and the market echoed."}},"required":["results","metadata"]}}}},"400":{"description":"Malformed code, a code that resolves to no city, or a page or pageSize outside its range – an out-of-range paging value is rejected, never clamped.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream schedules service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The schedules service is temporarily unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"query","name":"from","schema":{"type":"string","pattern":"^[A-Z]{3}$","example":"DXB"},"required":true,"description":"Departure city or airport code; an airport resolves to its city."},{"in":"query","name":"to","schema":{"type":"string","pattern":"^[A-Z]{3}$","example":"LHR"},"required":true,"description":"Arrival city or airport code; an airport resolves to its city."},{"in":"query","name":"airline","schema":{"type":"string","pattern":"^[A-Z0-9]{2}$"},"description":"Filter to one marketing carrier (e.g. SQ)."},{"in":"query","name":"siteCode","schema":{"type":"string","pattern":"^[A-Z]{2}$"},"description":"Wego market as a 2-letter code. Omitted, the API defaults to US and says so in metadata.siteCodeSource."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35,"example":"en"},"description":"Response language tag."},{"in":"query","name":"page","schema":{"default":1,"type":"integer","minimum":1,"maximum":20},"description":"Page number, 1-based (max 20). Defaults to 1. A timetable is a bounded list the API reads whole, so paging is rarely needed and the cap is low by design."},{"in":"query","name":"pageSize","schema":{"default":200,"type":"integer","minimum":1,"maximum":200},"description":"Rows per page (1-200). Defaults to 200, which is also the maximum, so most routes return whole on one page. A busier route exceeds it and says so with hasMore. pageSize exists to narrow a long answer, never to force paging."}]}},"/v1/flights/searches/{searchId}/results":{"get":{"operationId":"getFlightSearchResults","tags":["Flights"],"summary":"Read ranked flight results","description":"Ranked trips as lean list cards (default 10, max 50 per page; out-of-range rejected 400), filters + sort applied. No completion flag: re-read (300ms→3s) until snapshotFareCount holds steady across two reads AND snapshotTripCount > 0. No fares[] on a card – read the trip for fares.","responses":{"200":{"description":"The current ranked-trip snapshot, as list cards.","content":{"application/json":{"schema":{"type":"object","properties":{"searchId":{"type":"string","description":"The id of the search this snapshot belongs to."},"currencyCode":{"type":"string","description":"The currency the prices in this snapshot actually came back in, read off the fares themselves – so this, not metadata.currencyCode, is what a displayed number is denominated in. metadata.currencyCode reports what the read asked for and carries currencyCodeSource beside it; the two agree unless upstream declined to reprice into the requested currency."},"metadata":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"1-based page number of this snapshot."},"pageSize":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Trips requested per page."},"resultCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Trips on this page. The page only – judge a filter on totalCandidates, not this."},"totalCandidates":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Trips matching this read's filters across the whole snapshot – the count that judges a filter, never the page (results). Flights have no completion flag: settle on snapshotFareCount steady across two reads with snapshotTripCount above 0."},"hasMore":{"type":"boolean","description":"Another page of trips follows."},"filterOptions":{"type":"object","properties":{"alliances":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"The code the matching filter query param accepts."},"name":{"description":"Display label for the code, when the snapshot dictionary resolves one.","type":"string"},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Trips carrying this value, deduped per trip and matched on any leg."}},"required":["code","count"]},"description":"Alliance codes present in this snapshot, by descending count."},"airlines":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"The code the matching filter query param accepts."},"name":{"description":"Display label for the code, when the snapshot dictionary resolves one.","type":"string"},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Trips carrying this value, deduped per trip and matched on any leg."}},"required":["code","count"]},"description":"Airline codes present in this snapshot, by descending count."},"bookingSites":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"The code the matching filter query param accepts."},"name":{"description":"Display label for the code, when the snapshot dictionary resolves one.","type":"string"},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Trips carrying this value, deduped per trip and matched on any leg."}},"required":["code","count"]},"description":"Booking provider codes present in this snapshot, by descending count."},"stopoverAirports":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"The code the matching filter query param accepts."},"name":{"description":"Display label for the code, when the snapshot dictionary resolves one.","type":"string"},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Trips carrying this value, deduped per trip and matched on any leg."}},"required":["code","count"]},"description":"Stopover airport codes present in this snapshot, by descending count."},"aircraft":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"The code the matching filter query param accepts."},"name":{"description":"Display label for the code, when the snapshot dictionary resolves one.","type":"string"},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Trips carrying this value, deduped per trip and matched on any leg."}},"required":["code","count"]},"description":"Aircraft type codes present in this snapshot, by descending count. name is the display label the results card publishes (A380, A320 Neo), and is NOT unique: several codes can carry the same label, so filter on code. Includes any non-aircraft equipment upstream reports on a leg, such as BUS for a surface segment."},"stopoverDurations":{"description":"The layover span this snapshot carries, in minutes, measured the way min-stopover-duration and max-stopover-duration are judged: per trip, the LARGEST leg total across its legs. Use it to bound a slider. Both ends are reachable - sending the published min or max with no other filter keeps at least the trip that set it. min is 0 whenever the snapshot holds one direct trip, which is the usual case. A range, not a count list, so it has no name or count. Absent when nothing here is measurable: an empty snapshot, or one where every trip carries a connecting leg whose layover upstream never stated.","type":"object","properties":{"min":{"type":"number","minimum":0,"description":"Shortest layover any trip in this snapshot carries, in minutes. 0 whenever one trip is direct. Echoes the upstream figure, so it is a whole number of minutes wherever upstream states one."},"max":{"type":"number","minimum":0,"description":"Longest layover any trip in this snapshot carries, in minutes."}},"required":["min","max"]}},"required":["alliances","airlines","bookingSites","stopoverAirports","aircraft"],"description":"The filter values this snapshot actually carries, ordered by count, over the same trips as snapshotTripCount. Codes are what the matching query param accepts, so pick from here rather than guessing: sending one listed code with no other filter makes metadata.totalCandidates equal that count exactly. It does NOT bound results, which stays the requested page, so compare against totalCandidates and not resultCount. Counts assume the default matching, so airlines-match=all or same-airline=true can keep fewer trips than the airlines count promises. count is trips, not legs or fares, and a trip is counted once however many of its legs or fares carry the value, including when only its return leg does. name is the display label: always present on bookingSites, where the provider code is its own fallback, present on airlines and stopoverAirports only when the snapshot dictionary resolves the code, and never present on alliances, which upstream gives no label. Still growing while the search aggregates, so judge an ABSENT code only once snapshotFareCount holds steady across two reads."},"snapshotTripCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Renderable trips before filter/sort/page. 0 means upstream has produced none yet; above 0 beside an empty `results` means a filter or page range excluded everything. Settling needs `snapshotFareCount` steady across two reads AND this above 0."},"snapshotFareCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Upstream progress counter, for cross-read comparison only. Runs ahead of the fares returned and stays non-zero over an empty page, so read `resultCount`/`totalCandidates` for display. Settled = equal non-zero across two reads with `snapshotTripCount` above 0."},"createdAt":{"description":"When the upstream search was created (ISO 8601) – the freshness anchor for these prices. Absent when upstream omits it.","type":"string"},"currencyCode":{"type":"string","description":"The currency this read ASKED upstream for, and the one every price on it is meant to be in. Read it beside currencyCodeSource before you show a number: a price computed in the wrong currency renders as a perfectly normal price, with no error and no odd shape to notice, so the response states which one rather than leaving it to be inferred. Where the operation also publishes a top-level currencyCode, that field reports the currency the prices actually came back in; the two agree unless upstream declined to reprice."},"currencyCodeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved currencyCode: explicit (the caller sent currency – including a value equal to the default) or default (USD, no currency sent). A default here is the one signal that the request never carried the currency you meant."},"locale":{"type":"string","description":"The language tag this read asked upstream for – what any localized text on it was resolved in (room and board names, airline and airport names, review prose)."},"localeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved locale: explicit (the caller sent locale – including a value equal to the default) or default (en, no locale sent). A default here explains text that came back in a language the caller did not ask for."}},"required":["page","pageSize","resultCount","totalCandidates","hasMore","filterOptions","snapshotTripCount","snapshotFareCount","currencyCode","currencyCodeSource","locale","localeSource"],"description":"Pagination, the snapshot's filter vocabulary, the settle counters for this read, and what it resolved currency and locale to."},"results":{"type":"array","items":{"type":"object","properties":{"tripId":{"type":"string","description":"Opaque trip id; read it back with GET /v1/flights/trips/{tripId}."},"badges":{"type":"array","items":{"type":"string","enum":["best_value","cheapest","cheapest_direct"]},"description":"Every featured label that fits this trip. best_value ranks on the score of the trip's CHEAPEST fare, the same statistic sort=score_desc orders by; cheapest and cheapest_direct break a price tie by that score, then by leg-1 departure – cheapest keeps the LATER departure, cheapest_direct the EARLIER one."},"stops":{"type":"number","description":"Trip-level stop count – the MAX across legs, the same value ?stops= filters on. Do not fold legs[] yourself."},"durationMinutes":{"type":"number","description":"Trip-level duration – the SUM across legs."},"price":{"type":"object","properties":{"total":{"type":"number","description":"The cheapest fare's whole-party total (upstream totalAmount), including payment + booking fees."},"currency":{"type":"string","description":"ISO 4217 currency of total."},"scope":{"type":"string","const":"party","description":"total covers the whole party (adults + children + infants), not per-person."},"websiteCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"How many providers/fares sell this trip (\"from 11 websites\")."},"hasWegoFare":{"type":"boolean","description":"Whether any of the trip's fares is a Book-on-Wego fare."}},"required":["total","currency","scope","websiteCount","hasWegoFare"],"description":"Card price summary – the cheapest whole-party total, fee-inclusive; scope names the (party) figure."},"legs":{"type":"array","items":{"type":"object","properties":{"from":{"type":"string","description":"Departure airport IATA code."},"to":{"type":"string","description":"Arrival airport IATA code."},"departsAt":{"type":"string","description":"Leg departure, ISO 8601 with offset."},"arrivesAt":{"type":"string","description":"Leg arrival, ISO 8601 with offset."},"arrivalDayOffset":{"type":"number","description":"Calendar days the arrival lands after departure (the +1 badge)."},"overnight":{"type":"boolean","description":"The leg spans a night."},"durationMinutes":{"type":"number","description":"Leg duration in minutes."},"stops":{"type":"number","description":"Stops on this leg (0 is nonstop)."},"via":{"type":"array","items":{"type":"string"},"description":"Stopover airport codes (\"via KUL\"); empty for a direct leg."},"layoverMinutesByStop":{"description":"Layover minutes per connection, index-aligned to via, so layoverMinutesByStop[i] is the wait at via[i]. Absent on a direct leg, and absent on a leg whose upstream segment list does not line up with via, where publishing it could pair a wait with the wrong airport. Named apart from the trip read's legs[].layoverMinutes, which is a single leg TOTAL rather than a per-connection list. Sums to the leg total that min-stopover-duration and max-stopover-duration are judged against; those params bound the LARGEST leg total across the trip's legs, not an individual connection, so a trip kept by max-stopover-duration can still carry one long wait among several short ones.","type":"array","items":{"type":"number"}},"airlines":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"IATA airline code."},"name":{"type":"string","description":"Airline display name."},"logoUrl":{"type":"string","description":"Airline logo URL; may 404, fall back to the name."}},"required":["code","name","logoUrl"],"description":"An airline resolved to code, name and logo."},"description":"Marketing airlines on the leg, resolved to code, name and logo."},"operatingAirlines":{"description":"Carriers that fly a segment of this leg they do not market. airlines names the MARKETING carriers only, so on a leg sold by one airline every code here is a carrier absent from the ticket; on an interline leg sold by two, one of these may also market a different segment. Read this before telling a traveller who they fly. This card carries no segments[], so read the trip for which flight each one operates. PRESENT ONLY WHEN A SEGMENT PROVES A CODESHARE: absent means none was proven on this leg, never a promise that the marketing carrier operates every segment.","type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"IATA airline code."},"name":{"type":"string","description":"Airline display name."},"logoUrl":{"type":"string","description":"Airline logo URL; may 404, fall back to the name."}},"required":["code","name","logoUrl"],"description":"An airline resolved to code, name and logo."}},"aircraft":{"type":"array","items":{"type":"string"},"description":"Distinct aircraft short names across the leg (\"A330\",\"B787\"). Equipment only: a surface segment does show up here as its equipment label (\"Bus\"), but transportTypes is the field that states the mode."},"transportTypes":{"minItems":1,"type":"array","items":{"type":"string","enum":["FLIGHT","TRAIN","BUS","OTHER"],"description":"How this segment travels. FLIGHT is a plane. TRAIN and BUS are surface segments sold under a flight number, which airlines do publish, for example the Etihad coach between Dubai Bus Station and Abu Dhabi. OTHER is a mode upstream states that this API does not model, and is never a flight. Absent upstream is published as FLIGHT, which is what every segment meant before this field existed."},"description":"Distinct transport modes across this leg's segments, in segment order. Always present and never empty: an all-flight leg reads [\"FLIGHT\"]. This card carries no segments[], so anything else here means read the trip to see which segment is not a plane before you quote the leg as a flight."}},"required":["from","to","departsAt","arrivesAt","arrivalDayOffset","overnight","durationMinutes","stops","via","airlines","aircraft","transportTypes"]},"description":"Per-leg summary for this trip, outbound first then return."}},"required":["tripId","badges","stops","durationMinutes","price","legs"],"description":"One results-list trip: a price summary + per-leg summary, no fares[]. Read GET /v1/flights/trips/{tripId} for this trip's fares, fare ids and per-flight segments."},"description":"The requested page of ranked trips, as list cards."}},"required":["searchId","currencyCode","metadata","results"],"description":"A paginated snapshot of ranked trips as lean list cards."}}}},"400":{"description":"Invalid request body/query/path parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Unknown or expired search.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream flights service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The flights service is temporarily unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"searchId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}$"},"required":true,"description":"The opaque searchId returned by createFlightSearch. Ids expire (a few minutes); a 404 means the search is gone – create a new one."},{"in":"query","name":"page","schema":{"default":1,"type":"integer","minimum":1,"maximum":100},"description":"Page number, 1-based (max 100)."},{"in":"query","name":"pageSize","schema":{"default":10,"type":"integer","minimum":1,"maximum":50},"description":"Results per page (default 10, max 50); out-of-range values are rejected with 400, never clamped."},{"in":"query","name":"sort","schema":{"default":"score_desc","type":"string","enum":["score_desc","price_asc","duration_asc","leg1_departure_time_asc","leg1_departure_time_desc","leg2_departure_time_asc","leg2_departure_time_desc"]},"description":"Sort mode; score_desc (default) ranks by the metasearch score, the leg1/leg2 modes sort by that leg's local departure time."},{"in":"query","name":"airlines","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Airline IATA codes, repeat or comma-separate (e.g. ?airlines=SQ,TR). Values are OR'd together; by default a trip matches if ANY leg (outbound or return) carries any listed airline, not a trip-wide AND. Pass airlines-match=all for the trip-wide AND that wego.com applies."},{"in":"query","name":"airlines-match","schema":{"type":"string","enum":["any","all"]},"description":"How the airlines list is matched across a trip's legs. 'any' (the default) keeps a trip when ANY leg carries a listed airline. 'all' requires EVERY leg to, which is what wego.com does, so a Saudia-out / Emirates-back trip is dropped under ?airlines=EK. A leg marketed by two carriers still passes when one of them is listed, so add same-airline=true to require a single carrier as well. Ignored when airlines is absent."},{"in":"query","name":"same-airline","schema":{"type":"string","enum":["0","1","true","false"]},"description":"Keep only trips where every leg is marketed by exactly ONE airline and it is the same airline on every leg, so an interline or self-transfer leg marketed by two carriers is dropped. Matches the MARKETING carrier only: a codeshare, where one airline sells a flight another operates, is NOT excluded, because wego.com's 'same airline for the complete trip' does not exclude it either. Independent of airlines, so it works on its own; combine the two to mean 'only SQ, on every leg'."},{"in":"query","name":"alliances","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Alliance codes, repeat or comma-separate. OR'd together; a trip matches if ANY leg (outbound or return) belongs to any listed alliance. Case-insensitive. NOT a fixed set – read metadata.filterOptions.alliances for this snapshot's own values, which include upstream groupings that are not strictly alliances (lcc for low-cost carriers, value_alliance). An unknown code matches nothing rather than failing the request."},{"in":"query","name":"stops","schema":{"minItems":1,"type":"array","items":{"type":"integer","minimum":0,"maximum":9007199254740991}},"description":"Stop counts, repeat or comma-separate (e.g. ?stops=0,1). OR'd together; matched against the trip-level stop count (the MAX across legs, i.e. the value the response's stops field exposes) – not a per-leg check, so a mixed-stop round trip (e.g. a direct outbound + a 1-stop return) is kept under ?stops=1."},{"in":"query","name":"min-price","schema":{"type":"number","minimum":0},"description":"Minimum cheapest-fare total price (inclusive), in the response currency."},{"in":"query","name":"max-price","schema":{"type":"number","minimum":0},"description":"Maximum cheapest-fare total price (inclusive), in the response currency."},{"in":"query","name":"max-duration","schema":{"type":"integer","minimum":1,"maximum":9007199254740991},"description":"Maximum total trip duration in minutes (inclusive)."},{"in":"query","name":"min-stopover-duration","schema":{"type":"string","pattern":"^\\d{1,7}$"},"description":"Minimum layover time in minutes (inclusive), a floor on a trip's WORST leg. Judged per trip on the LARGEST leg total across its legs, the same fold stops applies, NOT on an individual connection: a leg with two 40-minute connections totals 80 and passes ?min-stopover-duration=60. A DIRECT trip has no layover, so it totals 0 and is DROPPED by any minimum above 0 - combine with ?stops=0 only if you want both. A trip whose layover upstream never stated is dropped by either bound rather than assumed to be 0. Read metadata.filterOptions.stopoverDurations for the span this snapshot carries, and judge the result on metadata.totalCandidates, never on the page."},{"in":"query","name":"max-stopover-duration","schema":{"type":"string","pattern":"^\\d{1,7}$"},"description":"Maximum layover time in minutes (inclusive), a ceiling on a trip's worst leg. It bounds how LONG a wait is, never when it falls: a 135-minute wait beginning 04:00 is under any sane ceiling and is still an overnight one, and a 465-minute wait beginning 11:00 is over it and never sees a night. To answer whether a wait falls overnight, read the connection's own clock from GET /v1/flights/trips/{tripId}?view=detail, whose segments carry arrivesAt and departsAt. Judged per trip on the LARGEST leg total across its legs, NOT on an individual connection: a 2-stop leg waiting 450 then 510 totals 960 and is dropped by ?max-stopover-duration=600 even though neither connection exceeds it. Direct trips total 0, so every maximum keeps them. Each card's legs[].layoverMinutesByStop carries the per-connection breakdown that sums to the total judged here. Read metadata.filterOptions.stopoverDurations for the span this snapshot carries, and judge the result on metadata.totalCandidates, never on the page."},{"in":"query","name":"outbound-departure-blocks","schema":{"minItems":1,"type":"array","items":{"type":"string","enum":["midnight","morning","afternoon","night"]}},"description":"Coarse local-time buckets for the OUTBOUND leg's DEPARTURE: midnight 00:00-05:59, morning 06:00-11:59, afternoon 12:00-17:59, night 18:00-23:59, local to that airport. Repeat or comma-separate; OR'd together. The four buckets PARTITION the day, so listing all four returns every trip exactly once - wego.com's own buckets overlap at 06:00, 12:00 and 18:00 and these do not. They are wider than they sound, so prefer the matching -range param whenever the caller gave a hard edge."},{"in":"query","name":"outbound-departure-range","schema":{"type":"string","pattern":"^(?:\\s*(?:\\d{1,3}|1[0-3]\\d{2}|14[0-2]\\d|143[0-9])\\s*-\\s*(?:\\d{1,3}|1[0-3]\\d{2}|14[0-2]\\d|143[0-9])\\s*)?$"},"description":"Minute-of-day window `min-max` (each 0-1439, local to that airport), both ends inclusive. When min > max the window wraps past midnight, e.g. 1320-360 means 22:00-06:00. Bounds the OUTBOUND leg's DEPARTURE, local to the departure airport."},{"in":"query","name":"outbound-arrival-blocks","schema":{"minItems":1,"type":"array","items":{"type":"string","enum":["midnight","morning","afternoon","night"]}},"description":"Coarse local-time buckets for when the OUTBOUND leg LANDS: midnight 00:00-05:59, morning 06:00-11:59, afternoon 12:00-17:59, night 18:00-23:59, local to that airport. Repeat or comma-separate; OR'd together. The four buckets PARTITION the day, so listing all four returns every trip exactly once - wego.com's own buckets overlap at 06:00, 12:00 and 18:00 and these do not. They are wider than they sound, so prefer the matching -range param whenever the caller gave a hard edge."},{"in":"query","name":"outbound-arrival-range","schema":{"type":"string","pattern":"^(?:\\s*(?:\\d{1,3}|1[0-3]\\d{2}|14[0-2]\\d|143[0-9])\\s*-\\s*(?:\\d{1,3}|1[0-3]\\d{2}|14[0-2]\\d|143[0-9])\\s*)?$"},"description":"Minute-of-day window `min-max` (each 0-1439, local to that airport), both ends inclusive. When min > max the window wraps past midnight, e.g. 1320-360 means 22:00-06:00. Bounds when the OUTBOUND leg LANDS, local to the ARRIVAL airport - this is the param for \"get me in before midnight\" or \"nothing that lands at 4am\". Judged on the clock ALONE, not the calendar: a red-eye landing 04:00 the NEXT day reads as minute 240 and is dropped by 360-1320 exactly as a same-day 04:00 landing would be, and kept by 0-1080 exactly as a same-day one would be. Read each card's legs[].arrivalDayOffset to tell the two apart."},{"in":"query","name":"return-departure-blocks","schema":{"minItems":1,"type":"array","items":{"type":"string","enum":["midnight","morning","afternoon","night"]}},"description":"Coarse local-time buckets for the RETURN leg's DEPARTURE: midnight 00:00-05:59, morning 06:00-11:59, afternoon 12:00-17:59, night 18:00-23:59, local to that airport. Repeat or comma-separate; OR'd together. The four buckets PARTITION the day, so listing all four returns every trip exactly once - wego.com's own buckets overlap at 06:00, 12:00 and 18:00 and these do not. They are wider than they sound, so prefer the matching -range param whenever the caller gave a hard edge. Applies to the RETURN leg (legs[1]) ONLY. A ONE-WAY search has no return leg, so any return-* bound judges a leg that does not exist and matches NOTHING - expect metadata.totalCandidates 0, which is the honest answer rather than a silently ignored filter. Judge the result on metadata.totalCandidates, never on the page."},{"in":"query","name":"return-departure-range","schema":{"type":"string","pattern":"^(?:\\s*(?:\\d{1,3}|1[0-3]\\d{2}|14[0-2]\\d|143[0-9])\\s*-\\s*(?:\\d{1,3}|1[0-3]\\d{2}|14[0-2]\\d|143[0-9])\\s*)?$"},"description":"Minute-of-day window `min-max` (each 0-1439, local to that airport), both ends inclusive. When min > max the window wraps past midnight, e.g. 1320-360 means 22:00-06:00. Bounds the RETURN leg's DEPARTURE, local to that leg's departure airport. Applies to the RETURN leg (legs[1]) ONLY. A ONE-WAY search has no return leg, so any return-* bound judges a leg that does not exist and matches NOTHING - expect metadata.totalCandidates 0, which is the honest answer rather than a silently ignored filter. Judge the result on metadata.totalCandidates, never on the page."},{"in":"query","name":"return-arrival-blocks","schema":{"minItems":1,"type":"array","items":{"type":"string","enum":["midnight","morning","afternoon","night"]}},"description":"Coarse local-time buckets for when the RETURN leg LANDS, i.e. when the traveller gets home: midnight 00:00-05:59, morning 06:00-11:59, afternoon 12:00-17:59, night 18:00-23:59, local to that airport. Repeat or comma-separate; OR'd together. The four buckets PARTITION the day, so listing all four returns every trip exactly once - wego.com's own buckets overlap at 06:00, 12:00 and 18:00 and these do not. They are wider than they sound, so prefer the matching -range param whenever the caller gave a hard edge. Applies to the RETURN leg (legs[1]) ONLY. A ONE-WAY search has no return leg, so any return-* bound judges a leg that does not exist and matches NOTHING - expect metadata.totalCandidates 0, which is the honest answer rather than a silently ignored filter. Judge the result on metadata.totalCandidates, never on the page."},{"in":"query","name":"return-arrival-range","schema":{"type":"string","pattern":"^(?:\\s*(?:\\d{1,3}|1[0-3]\\d{2}|14[0-2]\\d|143[0-9])\\s*-\\s*(?:\\d{1,3}|1[0-3]\\d{2}|14[0-2]\\d|143[0-9])\\s*)?$"},"description":"Minute-of-day window `min-max` (each 0-1439, local to that airport), both ends inclusive. When min > max the window wraps past midnight, e.g. 1320-360 means 22:00-06:00. Bounds when the RETURN leg LANDS, local to the ARRIVAL airport - the \"home before 22:00\" bound. Judged on the clock alone, not the calendar; read legs[].arrivalDayOffset to tell a next-day landing apart. Applies to the RETURN leg (legs[1]) ONLY. A ONE-WAY search has no return leg, so any return-* bound judges a leg that does not exist and matches NOTHING - expect metadata.totalCandidates 0, which is the honest answer rather than a silently ignored filter. Judge the result on metadata.totalCandidates, never on the page."},{"in":"query","name":"outbound-min-duration","schema":{"type":"string","pattern":"^\\d{1,7}$"},"description":"Minimum elapsed duration of the OUTBOUND leg in minutes (inclusive). Bounds ONE leg, unlike max-duration which bounds the whole trip."},{"in":"query","name":"outbound-max-duration","schema":{"type":"string","pattern":"^\\d{1,7}$"},"description":"Maximum elapsed duration of the OUTBOUND leg in minutes (inclusive). Bounds ONE leg, unlike max-duration which bounds the whole trip: a 3h outbound paired with a 14h return passes ?outbound-max-duration=300 and no trip-wide ceiling can express that."},{"in":"query","name":"return-min-duration","schema":{"type":"string","pattern":"^\\d{1,7}$"},"description":"Minimum elapsed duration of the RETURN leg in minutes (inclusive). Applies to the RETURN leg (legs[1]) ONLY. A ONE-WAY search has no return leg, so any return-* bound judges a leg that does not exist and matches NOTHING - expect metadata.totalCandidates 0, which is the honest answer rather than a silently ignored filter. Judge the result on metadata.totalCandidates, never on the page."},{"in":"query","name":"return-max-duration","schema":{"type":"string","pattern":"^\\d{1,7}$"},"description":"Maximum elapsed duration of the RETURN leg in minutes (inclusive). Bounds ONE leg, unlike max-duration which bounds the whole trip. Applies to the RETURN leg (legs[1]) ONLY. A ONE-WAY search has no return leg, so any return-* bound judges a leg that does not exist and matches NOTHING - expect metadata.totalCandidates 0, which is the honest answer rather than a silently ignored filter. Judge the result on metadata.totalCandidates, never on the page."},{"in":"query","name":"booking-types","schema":{"minItems":1,"type":"array","items":{"type":"string","enum":["wego","airline"]}},"description":"Booking types, repeat or comma-separate. OR'd together across the trip's fares – a trip matches if ANY of its fares has a listed kind (partner fares never match either value)."},{"in":"query","name":"booking-sites","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Provider codes, repeat or comma-separate. OR'd together across the trip's fares – a trip matches if ANY of its fares comes from a listed provider."},{"in":"query","name":"stopover-airports","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Stopover airport IATA codes, repeat or comma-separate. OR'd together; a trip matches if ANY leg (outbound or return) stops over at any listed airport."},{"in":"query","name":"aircraft","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Aircraft type CODES, repeat or comma-separate (e.g. ?aircraft=380,789). OR'd together; a trip matches if ANY leg (outbound or return) flies any listed type. These are upstream's short equipment codes (380, 789, 32N), NOT the display labels the results card publishes (A380, B787-9, A320 Neo) – read metadata.filterOptions.aircraft for this snapshot's codes and the label beside each one. Several codes can share one label (321 and 32S are both A321), which is why the code is the filter key. Case-insensitive. NOT a fixed set; an unknown code matches nothing rather than failing the request."},{"in":"query","name":"currency","schema":{"default":"USD","type":"string","pattern":"^[A-Z]{3}$","example":"AED"},"description":"Pricing currency as a 3-letter ISO 4217 code (e.g. AED). Optional; defaults to USD server-side. Not inherited from the search: a search created in one currency reads back in USD unless you pass currency on every read, so re-send the search's currency to keep prices in it."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35,"example":"en"},"description":"Response language tag (e.g. en, ar). Optional; defaults to en server-side. Not inherited from the search – pass it on each read to keep results in that language."},{"in":"query","name":"view","schema":{"default":"card","type":"string","enum":["card"]},"description":"Response projection. `card` is the only value: the lean results-list projection (cheapest-price summary + trip-level stops/duration + per-leg airline/aircraft/stopover, no fares[]). The former `default` projection was removed in issue #1308 – read GET /v1/flights/trips/{tripId} for a trip's fares and segments."}]}},"/v1/flights/trips/{tripId}":{"get":{"operationId":"getFlightTrip","tags":["Flights"],"summary":"Open one flight trip","description":"Returns one trip's full itinerary and every fare on it (each kind-tagged), for the given tripId within its searchId. searchId is required – it comes from the search/results snapshot the tripId was read from.","responses":{"200":{"description":"The full itinerary + all fares for the trip (agent default) or, with ?view=detail, the per-segment detail projection.","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"tripId":{"type":"string","description":"Opaque trip id, shaped {searchId}:{tripCode}; read it back with GET /v1/flights/trips/{tripId}."},"featured":{"description":"The single highest-priority featured label, best_value > cheapest > cheapest_direct. best_value ranks on the score of the trip's CHEAPEST fare, the same statistic sort=score_desc orders by, so on a score_desc read it lands on the first result. cheapest and cheapest_direct break a price tie by that same score, then by leg-1 departure – cheapest keeps the LATER departure, cheapest_direct the EARLIER one. Under sort=score_desc the two picks are also MOVED toward positions 2 and 3, matching wego.com. Those positions are targets, not guarantees: a pick already at or above its target stays put, and cheapest_direct lands at position 2 when the first result is itself at the cheapest price.","type":"string","enum":["best_value","cheapest","cheapest_direct"]},"stops":{"type":"number","description":"Trip-level stop count, the max across legs."},"durationMinutes":{"type":"number","description":"Total trip duration in minutes, summed across legs."},"outbound":{"type":"object","properties":{"from":{"type":"string","description":"Departure airport IATA code."},"to":{"type":"string","description":"Arrival airport IATA code."},"departsAt":{"type":"string","description":"Leg departure, ISO 8601 with offset."},"arrivesAt":{"type":"string","description":"Leg arrival, ISO 8601 with offset."},"durationMinutes":{"type":"number","description":"Total leg duration in minutes."},"stops":{"type":"number","description":"Stops on this leg (0 is nonstop)."},"airlines":{"type":"array","items":{"type":"string"},"description":"Raw IATA airline codes on the leg (unchanged). See airlinesDetail for display names."},"airlinesDetail":{"description":"Index-aligned display names for `airlines`, resolved from the search snapshot's airline dictionary – the same join the results cards use; the bare code is the fallback on a dictionary miss. Omitted when the leg carries no airline codes. Additive – raw `airlines` codes are unaffected.","type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"IATA airline code (the operating/marketing carrier)."},"name":{"type":"string","description":"Airline display name from the search snapshot's airline dictionary; the bare code is the fallback on a dictionary miss."}},"required":["code","name"],"description":"A carrier resolved to code + display name. This is airline identity (who operates/markets the flight), NOT the booking provider that sells the ticket – that is fares[].providerName / providerCode."}},"operatingAirlines":{"description":"Carriers that fly a segment of this leg they do not market. airlines / airlinesDetail name the MARKETING carriers only, so on a leg sold by one airline every code here is a carrier absent from the ticket; on an interline leg sold by two, one of these may also market a different segment. Read this before telling a traveller who they fly – mileage accrual, lounge access and baggage rules follow the operating carrier. Distinct codes, folded from this leg's own segments; pair it with segments[] to see which flight each one operates. PRESENT ONLY WHEN A SEGMENT PROVES A CODESHARE: absent means none was proven on this leg, never a promise that the marketing carrier operates every segment.","type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"IATA airline code (the operating/marketing carrier)."},"name":{"type":"string","description":"Airline display name from the search snapshot's airline dictionary; the bare code is the fallback on a dictionary miss."}},"required":["code","name"],"description":"A carrier resolved to code + display name. This is airline identity (who operates/markets the flight), NOT the booking provider that sells the ticket – that is fares[].providerName / providerCode."}},"transportTypes":{"minItems":1,"type":"array","items":{"type":"string","enum":["FLIGHT","TRAIN","BUS","OTHER"],"description":"How this segment travels. FLIGHT is a plane. TRAIN and BUS are surface segments sold under a flight number, which airlines do publish, for example the Etihad coach between Dubai Bus Station and Abu Dhabi. OTHER is a mode upstream states that this API does not model, and is never a flight. Absent upstream is published as FLIGHT, which is what every segment meant before this field existed."},"description":"Distinct transport modes across this leg's segments, in segment order. Always present and never empty: an all-flight leg reads [\"FLIGHT\"], so one read of this field replaces folding segments[] yourself. Anything else means part of this leg is not a plane, and segments[] says which part."},"segments":{"description":"Per-segment identity (marketing/operating carrier, flight number, times, transport mode); omitted when upstream carries no segments for the leg. Previously reachable only via ?view=detail.","type":"array","items":{"type":"object","properties":{"from":{"type":"string","description":"Departure airport IATA code."},"to":{"type":"string","description":"Arrival airport IATA code."},"departsAt":{"type":"string","description":"Segment departure, ISO 8601 with offset."},"arrivesAt":{"type":"string","description":"Segment arrival, ISO 8601 with offset."},"marketingCarrier":{"type":"object","properties":{"code":{"type":"string","description":"IATA airline code (the operating/marketing carrier)."},"name":{"type":"string","description":"Airline display name from the search snapshot's airline dictionary; the bare code is the fallback on a dictionary miss."}},"required":["code","name"],"description":"The carrier whose code is on the ticket (the marketing / flight-number airline)."},"operatingCarrier":{"description":"The carrier that actually operates the flight – present ONLY on a codeshare, when it differs from the marketing carrier.","type":"object","properties":{"code":{"type":"string","description":"IATA airline code (the operating/marketing carrier)."},"name":{"type":"string","description":"Airline display name from the search snapshot's airline dictionary; the bare code is the fallback on a dictionary miss."}},"required":["code","name"]},"flightNumber":{"type":"string","description":"The marketing flight designator, e.g. SQ12."},"transportType":{"type":"string","enum":["FLIGHT","TRAIN","BUS","OTHER"],"description":"How this segment travels. FLIGHT is a plane. TRAIN and BUS are surface segments sold under a flight number, which airlines do publish, for example the Etihad coach between Dubai Bus Station and Abu Dhabi. OTHER is a mode upstream states that this API does not model, and is never a flight. Absent upstream is published as FLIGHT, which is what every segment meant before this field existed."},"fromName":{"description":"Departure endpoint's display name, when upstream hydrates it on the segment, for example \"Dubai Bus Station\" beside the bare XNB. Absent rather than guessed when upstream carries no name.","type":"string"},"toName":{"description":"Arrival endpoint's display name, when upstream hydrates it on the segment.","type":"string"},"fromStationType":{"description":"What kind of place the departure endpoint is, when upstream states it. Absent means unstated, never airport: read transportType, which is always present, to judge whether this segment is a flight.","type":"string","enum":["airport","bus_station","train_station","other"]},"toStationType":{"description":"What kind of place the arrival endpoint is, when upstream states it. Absent means unstated, never airport.","type":"string","enum":["airport","bus_station","train_station","other"]}},"required":["from","to","departsAt","arrivesAt","marketingCarrier","flightNumber","transportType"],"description":"One segment within a leg, carrying canonical carrier identity (marketing vs operating), absolute times and its transport mode – the inputs a codeshare-aware handoff-identity check needs, plus what issue 42 added: whether this segment is a plane at all."}}},"required":["from","to","departsAt","arrivesAt","durationMinutes","stops","airlines","transportTypes"],"description":"The outbound leg."},"return":{"type":"object","properties":{"from":{"type":"string","description":"Departure airport IATA code."},"to":{"type":"string","description":"Arrival airport IATA code."},"departsAt":{"type":"string","description":"Leg departure, ISO 8601 with offset."},"arrivesAt":{"type":"string","description":"Leg arrival, ISO 8601 with offset."},"durationMinutes":{"type":"number","description":"Total leg duration in minutes."},"stops":{"type":"number","description":"Stops on this leg (0 is nonstop)."},"airlines":{"type":"array","items":{"type":"string"},"description":"Raw IATA airline codes on the leg (unchanged). See airlinesDetail for display names."},"airlinesDetail":{"description":"Index-aligned display names for `airlines`, resolved from the search snapshot's airline dictionary – the same join the results cards use; the bare code is the fallback on a dictionary miss. Omitted when the leg carries no airline codes. Additive – raw `airlines` codes are unaffected.","type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"IATA airline code (the operating/marketing carrier)."},"name":{"type":"string","description":"Airline display name from the search snapshot's airline dictionary; the bare code is the fallback on a dictionary miss."}},"required":["code","name"],"description":"A carrier resolved to code + display name. This is airline identity (who operates/markets the flight), NOT the booking provider that sells the ticket – that is fares[].providerName / providerCode."}},"operatingAirlines":{"description":"Carriers that fly a segment of this leg they do not market. airlines / airlinesDetail name the MARKETING carriers only, so on a leg sold by one airline every code here is a carrier absent from the ticket; on an interline leg sold by two, one of these may also market a different segment. Read this before telling a traveller who they fly – mileage accrual, lounge access and baggage rules follow the operating carrier. Distinct codes, folded from this leg's own segments; pair it with segments[] to see which flight each one operates. PRESENT ONLY WHEN A SEGMENT PROVES A CODESHARE: absent means none was proven on this leg, never a promise that the marketing carrier operates every segment.","type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"IATA airline code (the operating/marketing carrier)."},"name":{"type":"string","description":"Airline display name from the search snapshot's airline dictionary; the bare code is the fallback on a dictionary miss."}},"required":["code","name"],"description":"A carrier resolved to code + display name. This is airline identity (who operates/markets the flight), NOT the booking provider that sells the ticket – that is fares[].providerName / providerCode."}},"transportTypes":{"minItems":1,"type":"array","items":{"type":"string","enum":["FLIGHT","TRAIN","BUS","OTHER"],"description":"How this segment travels. FLIGHT is a plane. TRAIN and BUS are surface segments sold under a flight number, which airlines do publish, for example the Etihad coach between Dubai Bus Station and Abu Dhabi. OTHER is a mode upstream states that this API does not model, and is never a flight. Absent upstream is published as FLIGHT, which is what every segment meant before this field existed."},"description":"Distinct transport modes across this leg's segments, in segment order. Always present and never empty: an all-flight leg reads [\"FLIGHT\"], so one read of this field replaces folding segments[] yourself. Anything else means part of this leg is not a plane, and segments[] says which part."},"segments":{"description":"Per-segment identity (marketing/operating carrier, flight number, times, transport mode); omitted when upstream carries no segments for the leg. Previously reachable only via ?view=detail.","type":"array","items":{"type":"object","properties":{"from":{"type":"string","description":"Departure airport IATA code."},"to":{"type":"string","description":"Arrival airport IATA code."},"departsAt":{"type":"string","description":"Segment departure, ISO 8601 with offset."},"arrivesAt":{"type":"string","description":"Segment arrival, ISO 8601 with offset."},"marketingCarrier":{"type":"object","properties":{"code":{"type":"string","description":"IATA airline code (the operating/marketing carrier)."},"name":{"type":"string","description":"Airline display name from the search snapshot's airline dictionary; the bare code is the fallback on a dictionary miss."}},"required":["code","name"],"description":"The carrier whose code is on the ticket (the marketing / flight-number airline)."},"operatingCarrier":{"description":"The carrier that actually operates the flight – present ONLY on a codeshare, when it differs from the marketing carrier.","type":"object","properties":{"code":{"type":"string","description":"IATA airline code (the operating/marketing carrier)."},"name":{"type":"string","description":"Airline display name from the search snapshot's airline dictionary; the bare code is the fallback on a dictionary miss."}},"required":["code","name"]},"flightNumber":{"type":"string","description":"The marketing flight designator, e.g. SQ12."},"transportType":{"type":"string","enum":["FLIGHT","TRAIN","BUS","OTHER"],"description":"How this segment travels. FLIGHT is a plane. TRAIN and BUS are surface segments sold under a flight number, which airlines do publish, for example the Etihad coach between Dubai Bus Station and Abu Dhabi. OTHER is a mode upstream states that this API does not model, and is never a flight. Absent upstream is published as FLIGHT, which is what every segment meant before this field existed."},"fromName":{"description":"Departure endpoint's display name, when upstream hydrates it on the segment, for example \"Dubai Bus Station\" beside the bare XNB. Absent rather than guessed when upstream carries no name.","type":"string"},"toName":{"description":"Arrival endpoint's display name, when upstream hydrates it on the segment.","type":"string"},"fromStationType":{"description":"What kind of place the departure endpoint is, when upstream states it. Absent means unstated, never airport: read transportType, which is always present, to judge whether this segment is a flight.","type":"string","enum":["airport","bus_station","train_station","other"]},"toStationType":{"description":"What kind of place the arrival endpoint is, when upstream states it. Absent means unstated, never airport.","type":"string","enum":["airport","bus_station","train_station","other"]}},"required":["from","to","departsAt","arrivesAt","marketingCarrier","flightNumber","transportType"],"description":"One segment within a leg, carrying canonical carrier identity (marketing vs operating), absolute times and its transport mode – the inputs a codeshare-aware handoff-identity check needs, plus what issue 42 added: whether this segment is a plane at all."}}},"required":["from","to","departsAt","arrivesAt","durationMinutes","stops","airlines","transportTypes"],"description":"The return leg; present only on a round trip."},"fares":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["wego","airline","partner"],"description":"Fare source: wego (Book-on-Wego), airline (booked with the carrier) or partner (an OTA)."},"fareId":{"type":"string","description":"Opaque fare id; pass it to the fare-options and booking-link routes."},"providerCode":{"type":"string","description":"Booking provider code (the OTA or airline selling this fare)."},"providerName":{"type":"string","description":"Booking provider display name."},"price":{"type":"object","properties":{"total":{"type":"number","description":"Whole-party total (adults + children + infants) in the requested currency, including payment + booking fees. No separate tax breakdown at search time: the fare options read carries a tax figure, set by the cabin rather than by the individual fare option."},"totalUsd":{"type":"number","description":"Whole-party total in USD (the cheapest-first sort key), including fees."},"currency":{"type":"string","description":"ISO 4217 currency of total."},"scope":{"type":"string","const":"party","description":"total covers the whole party (adults + children + infants), not per-person."},"includesFees":{"type":"boolean","const":true,"description":"total includes payment + booking fees; there is no separate tax breakdown at search time."}},"required":["total","totalUsd","currency","scope","includesFees"],"description":"Fare price. total/totalUsd are the whole-party amount, fee-inclusive, and are the authoritative figure for this fare. Search-time fares carry no per-passenger breakdown."},"refundable":{"type":"boolean","description":"Whether this fare is refundable, as the provider states it."},"hasFareOptions":{"type":"boolean","description":"Whether GET /v1/flights/fares/{fareId}/options can expand this fare into branded options."},"handoffUrl":{"type":"string","description":"Deep link that hands the booking off to the provider or wego.com checkout for this fare."}},"required":["kind","fareId","providerCode","providerName","price","refundable","hasFareOptions","handoffUrl"]},"description":"Bookable fares for this trip, cheapest-first."},"metadata":{"type":"object","properties":{"currencyCode":{"type":"string","description":"The currency this read ASKED upstream for, and the one every price on it is meant to be in. Read it beside currencyCodeSource before you show a number: a price computed in the wrong currency renders as a perfectly normal price, with no error and no odd shape to notice, so the response states which one rather than leaving it to be inferred. Where the operation also publishes a top-level currencyCode, that field reports the currency the prices actually came back in; the two agree unless upstream declined to reprice."},"currencyCodeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved currencyCode: explicit (the caller sent currency – including a value equal to the default) or default (USD, no currency sent). A default here is the one signal that the request never carried the currency you meant."},"locale":{"type":"string","description":"The language tag this read asked upstream for – what any localized text on it was resolved in (room and board names, airline and airport names, review prose)."},"localeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved locale: explicit (the caller sent locale – including a value equal to the default) or default (en, no locale sent). A default here explains text that came back in a language the caller did not ask for."}},"required":["currencyCode","currencyCodeSource","locale","localeSource"],"description":"What this read resolved currency and locale to, and how each was decided."}},"required":["tripId","stops","durationMinutes","outbound","fares","metadata"],"description":"One itinerary carrying its fares (cheapest-first), and what this read resolved currency and locale to."},{"type":"object","properties":{"tripId":{"type":"string","description":"Opaque trip id, shaped {searchId}:{tripCode}; read it back with GET /v1/flights/trips/{tripId}."},"stops":{"type":"number","description":"Trip-level stop count, the max across legs."},"durationMinutes":{"type":"number","description":"Total trip duration in minutes, summed across legs."},"legs":{"type":"array","items":{"type":"object","properties":{"direction":{"type":"string","enum":["depart","return"],"description":"Which leg this is: depart or return."},"from":{"type":"object","properties":{"code":{"type":"string","description":"Airport IATA code."},"name":{"type":"string","description":"Airport display name from the snapshot's places."},"city":{"description":"City name, when the dictionary has it.","type":"string"},"stationType":{"description":"What kind of place this is, when upstream states it. Absent means unstated, never airport.","type":"string","enum":["airport","bus_station","train_station","other"]}},"required":["code","name"],"description":"Departure airport for this leg."},"to":{"type":"object","properties":{"code":{"type":"string","description":"Airport IATA code."},"name":{"type":"string","description":"Airport display name from the snapshot's places."},"city":{"description":"City name, when the dictionary has it.","type":"string"},"stationType":{"description":"What kind of place this is, when upstream states it. Absent means unstated, never airport.","type":"string","enum":["airport","bus_station","train_station","other"]}},"required":["code","name"],"description":"Arrival airport for this leg."},"departsAt":{"type":"string","description":"Leg departure, ISO 8601 with offset."},"arrivesAt":{"type":"string","description":"Leg arrival, ISO 8601 with offset."},"arrivalDayOffset":{"type":"number","description":"Calendar days the arrival lands after departure (the +1 badge)."},"overnight":{"type":"boolean","description":"The leg spans a night."},"durationMinutes":{"type":"number","description":"Leg duration in minutes."},"stops":{"type":"number","description":"Stops on this leg (0 is nonstop)."},"layoverMinutes":{"description":"Total layover across the leg's stops, minutes.","type":"number"},"segments":{"type":"array","items":{"type":"object","properties":{"from":{"type":"object","properties":{"code":{"type":"string","description":"Airport IATA code."},"name":{"type":"string","description":"Airport display name from the snapshot's places."},"city":{"description":"City name, when the dictionary has it.","type":"string"},"stationType":{"description":"What kind of place this is, when upstream states it. Absent means unstated, never airport.","type":"string","enum":["airport","bus_station","train_station","other"]}},"required":["code","name"],"description":"Departure airport for this segment."},"to":{"type":"object","properties":{"code":{"type":"string","description":"Airport IATA code."},"name":{"type":"string","description":"Airport display name from the snapshot's places."},"city":{"description":"City name, when the dictionary has it.","type":"string"},"stationType":{"description":"What kind of place this is, when upstream states it. Absent means unstated, never airport.","type":"string","enum":["airport","bus_station","train_station","other"]}},"required":["code","name"],"description":"Arrival airport for this segment."},"departsAt":{"type":"string","description":"Segment departure, ISO 8601 with offset."},"arrivesAt":{"type":"string","description":"Segment arrival, ISO 8601 with offset."},"durationMinutes":{"type":"number","description":"Segment duration in minutes."},"airline":{"type":"object","properties":{"code":{"type":"string","description":"IATA airline code."},"name":{"type":"string","description":"Airline display name."},"logoUrl":{"type":"string","description":"Airline logo URL; may 404, fall back to the name."}},"required":["code","name","logoUrl"],"description":"An airline resolved to code, name and logo."},"operatedBy":{"description":"Present only when the operating carrier differs.","type":"string"},"flightNumber":{"type":"string","description":"Marketing flight designator, e.g. SQ12."},"aircraft":{"type":"string","description":"Aircraft type name, e.g. Boeing 777-300ER. On a surface segment this carries the equipment label (\"Bus\"); transportType is the typed form of the same fact."},"transportType":{"type":"string","enum":["FLIGHT","TRAIN","BUS","OTHER"],"description":"How this segment travels. FLIGHT is a plane. TRAIN and BUS are surface segments sold under a flight number, which airlines do publish, for example the Etihad coach between Dubai Bus Station and Abu Dhabi. OTHER is a mode upstream states that this API does not model, and is never a flight. Absent upstream is published as FLIGHT, which is what every segment meant before this field existed."},"cabin":{"type":"string","description":"Cabin class on this segment."},"amenities":{"type":"object","properties":{"wifi":{"type":"object","properties":{"text":{"type":"string","description":"Human label for the amenity."},"exists":{"type":"boolean","description":"The amenity is present on the segment."},"free":{"type":"boolean","description":"The amenity is included, not sold as an extra."}},"required":["text","exists","free"],"description":"One cabin amenity on a segment."},"power":{"type":"object","properties":{"text":{"type":"string","description":"Human label for the amenity."},"exists":{"type":"boolean","description":"The amenity is present on the segment."},"free":{"type":"boolean","description":"The amenity is included, not sold as an extra."}},"required":["text","exists","free"],"description":"One cabin amenity on a segment."},"entertainment":{"type":"object","properties":{"text":{"type":"string","description":"Human label for the amenity."},"exists":{"type":"boolean","description":"The amenity is present on the segment."},"free":{"type":"boolean","description":"The amenity is included, not sold as an extra."}},"required":["text","exists","free"],"description":"One cabin amenity on a segment."},"meal":{"type":"object","properties":{"text":{"type":"string","description":"Human label for the amenity."},"exists":{"type":"boolean","description":"The amenity is present on the segment."},"free":{"type":"boolean","description":"The amenity is included, not sold as an extra."}},"required":["text","exists","free"],"description":"One cabin amenity on a segment."}},"description":"Cabin amenities on the segment, by kind; each present only when upstream reports it."},"seat":{"type":"object","properties":{"pitch":{"description":"Seat pitch, e.g. \"76 cm seat pitch\".","type":"string"},"layout":{"description":"Row layout, e.g. \"3-3-3\".","type":"string"}},"description":"Seat pitch and layout for the segment, when reported."}},"required":["from","to","departsAt","arrivesAt","durationMinutes","airline","flightNumber","aircraft","transportType","cabin"],"description":"One flight segment as the detail view renders it."},"description":"The individual flights that make up this leg."}},"required":["direction","from","to","departsAt","arrivesAt","arrivalDayOffset","overnight","durationMinutes","stops","segments"]},"description":"The trip's legs with per-segment detail, outbound first."},"fares":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["wego","airline","partner"],"description":"Fare source: wego (Book-on-Wego), airline (booked with the carrier) or partner (an OTA)."},"fareId":{"type":"string","description":"Opaque fare id; pass it to the fare-options and booking-link routes."},"provider":{"type":"object","properties":{"code":{"type":"string","description":"Booking provider (OTA/airline) code."},"name":{"type":"string","description":"Provider display name."},"logoUrl":{"type":"string","description":"Synthesized provider logo URL; may 404 – fall back to name."},"color":{"description":"Provider brand color, when known.","type":"string"}},"required":["code","name","logoUrl"],"description":"A booking provider resolved to code, name, logo and brand color."},"price":{"type":"object","properties":{"total":{"type":"number","description":"Whole-party total, including payment + booking fees."},"totalUsd":{"type":"number","description":"Whole-party total in USD."},"currency":{"type":"string","description":"ISO 4217 currency of total."},"includesTaxesAndFees":{"type":"boolean","description":"Upstream totals are fee-inclusive; no separate tax breakdown at search time."}},"required":["total","totalUsd","currency","includesTaxesAndFees"],"description":"Fare price for the whole party, fee-inclusive."},"baggage":{"description":"Baggage allowance for this fare, when the provider states it.","type":"object","properties":{"cabin":{"description":"Cabin baggage allowance display string.","type":"string"},"checked":{"description":"Checked baggage allowance display string.","type":"string"}}},"refundable":{"type":"boolean","description":"Whether this fare is refundable, as the provider states it."},"hasFareOptions":{"type":"boolean","description":"Whether GET /v1/flights/fares/{fareId}/options can expand this fare into branded options."},"handoffUrl":{"type":"string","description":"Deep link to the provider or wego.com checkout for this fare."}},"required":["kind","fareId","provider","price","refundable","hasFareOptions","handoffUrl"],"description":"One bookable fare in the detail view (carries provider brand)."},"description":"Bookable fares for this trip, cheapest-first."},"metadata":{"type":"object","properties":{"currencyCode":{"type":"string","description":"The currency this read ASKED upstream for, and the one every price on it is meant to be in. Read it beside currencyCodeSource before you show a number: a price computed in the wrong currency renders as a perfectly normal price, with no error and no odd shape to notice, so the response states which one rather than leaving it to be inferred. Where the operation also publishes a top-level currencyCode, that field reports the currency the prices actually came back in; the two agree unless upstream declined to reprice."},"currencyCodeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved currencyCode: explicit (the caller sent currency – including a value equal to the default) or default (USD, no currency sent). A default here is the one signal that the request never carried the currency you meant."},"locale":{"type":"string","description":"The language tag this read asked upstream for – what any localized text on it was resolved in (room and board names, airline and airport names, review prose)."},"localeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved locale: explicit (the caller sent locale – including a value equal to the default) or default (en, no locale sent). A default here explains text that came back in a language the caller did not ask for."}},"required":["currencyCode","currencyCodeSource","locale","localeSource"],"description":"What this read resolved currency and locale to, and how each was decided."}},"required":["tripId","stops","durationMinutes","legs","fares","metadata"],"description":"One itinerary with per-segment detail, amenities and seat metadata (?view=detail), and what this read resolved currency and locale to."}]}}}},"400":{"description":"Invalid request body/query/path parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Unknown or expired trip. Ids are context-bound: a tripId resolves only with the searchId it came from, and retrying an expired one never recovers - create a new search and rethread.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream flights service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The flights service is temporarily unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"tripId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}$"},"required":true,"description":"The trip id from a search-results snapshot, shaped {searchId}:{tripCode}. It resolves only together with the searchId it came from, and both expire with the search."},{"in":"query","name":"searchId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}$"},"required":true,"description":"Required search context: the searchId the tripId was read from (it is the tripId's first :-segment). A trip id resolves only with its own search, and both expire together."},{"in":"query","name":"currency","schema":{"default":"USD","type":"string","pattern":"^[A-Z]{3}$","example":"AED"},"description":"Pricing currency as a 3-letter ISO 4217 code (e.g. AED). Optional; defaults to USD server-side. Not inherited from the search: a search created in one currency reads back in USD unless you pass currency on every read, so re-send the search's currency to keep prices in it."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35,"example":"en"},"description":"Response language tag (e.g. en, ar). Optional; defaults to en server-side. Not inherited from the search – pass it on each read to keep results in that language."},{"in":"query","name":"view","schema":{"default":"default","type":"string","enum":["default","detail"]},"description":"Response projection. default (agent shape): the full itinerary with every fare and per-flight segments. detail: the richer UI view-model (per-segment amenities, seat metadata, provider brand). Defaults to default."}]}},"/v1/flights/trips/{tripId}/experience":{"get":{"operationId":"getTripExperience","tags":["Flights"],"summary":"Read a trip's experience signals","description":"Per-leg signals for what a journey is like to sit through: overnight, longStopover, earlyDeparture, lateArrival, plus positive-only witnesses for a tight connection, aircraft age and carrier rating. No score - Recommended sort ranks on the price-adjusted fare score, which one would not match.","responses":{"200":{"description":"The trip's per-leg signals. A witness field that is absent was not asserted; it is not a negative.","content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"The trip these signals are for."},"legs":{"minItems":1,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The upstream leg id, e.g. `SIN-BKK:TR638~3:0`."},"departureAirportCode":{"type":"string","description":"Departure airport IATA code."},"arrivalAirportCode":{"type":"string","description":"Arrival airport IATA code."},"stopsCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Stopovers on this leg – what makes an absent shortStopover readable."},"signals":{"type":"object","properties":{"overnight":{"type":"boolean","description":"The leg spans a night."},"longStopover":{"type":"boolean","description":"A stopover long enough to be a wait rather than a connection."},"earlyDeparture":{"type":"boolean","description":"Departs early enough to cost a night's sleep."},"lateArrival":{"type":"boolean","description":"Arrives late enough to cost one."},"shortStopover":{"description":"A connection tight enough to be a risk. Omitted on a leg with no stopover: upstream reports every nonstop leg as a short stopover, which carries no information.","type":"boolean","const":true},"newAircraft":{"description":"Present only when asserted; absent means not asserted.","type":"boolean","const":true},"highlyRatedCarrier":{"description":"Present only when asserted; absent means not asserted.","type":"boolean","const":true}},"required":["overnight","longStopover","earlyDeparture","lateArrival"],"description":"Per-leg comfort signals. newAircraft and highlyRatedCarrier are positive-only witnesses: present only when upstream asserts them, absent otherwise; absent is never a negative."}},"required":["id","departureAirportCode","arrivalAirportCode","stopsCount","signals"]},"description":"Per-leg comfort signals, in itinerary order (outbound first)."},"metadata":{"type":"object","properties":{"legCount":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Legs on this trip: 1 one-way, 2 a return."}},"required":["legCount"],"description":"Trip-level experience metadata."}},"required":["tripId","legs","metadata"]}}}},"400":{"description":"Invalid request body/query/path parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Unknown trip, or its search has expired. Search again and re-open the trip - retrying the same id never recovers.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream flights service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The flights service is temporarily unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"tripId","schema":{"type":"string","maxLength":256,"pattern":"^[A-Za-z0-9._~=-]+:[A-Za-z0-9._:~=-]+$"},"required":true,"description":"The trip from a flight-search result. Shaped {searchId}:{tripCode}."},{"in":"query","name":"searchId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}$"},"description":"Optional cross-check. When supplied it must equal the tripId's first :-segment; a mismatch is rejected."}]}},"/v1/flights/fares/{fareId}/options":{"get":{"operationId":"getFareOptions","tags":["Flights"],"summary":"List a fare's options","description":"Returns a Book-on-Wego fare's bookable options (price, baggage, refundability, penalties, and the carrier's terms links when it publishes any), ordered by leg then price. A multi-leg trip needs ONE option per leg - read the top-level price, never min(options). Non-wego fareId 400; stale 404.","responses":{"200":{"description":"The fare options, ordered by leg then cheapest-first within a leg.","content":{"application/json":{"schema":{"type":"object","properties":{"fareId":{"type":"string","description":"The fare id these options are for."},"currencyCode":{"type":"string","description":"The currency the prices are quoted in – the same value as metadata.currencyCode, which carries currencyCodeSource beside it. Unlike the results read, this route quotes in the currency it asked for, so the two cannot disagree."},"price":{"type":"object","properties":{"total":{"type":"number","description":"Whole-trip, whole-party total for the cheapest combination of options, as the upstream states it. Not computed here."},"totalUsd":{"description":"The same figure in USD.","type":"number"},"currency":{"type":"string","description":"ISO 4217 currency of total."},"scope":{"type":"string","const":"party","description":"total covers the whole party (adults + children + infants), not per-person."}},"required":["total","currency","scope"],"description":"The whole-trip price for this fare, so no caller has to add per-leg options together. Absent when the upstream did not state one."},"legs":{"description":"The legs the options are attributed to, in upstream leg order. Present only when every option carries a legId that matches one of them.","type":"array","items":{"type":"object","properties":{"legId":{"type":"number","description":"The leg this group of options prices, as the upstream numbers it (1 = the first leg)."},"from":{"description":"Departure airport code of the leg.","type":"string"},"to":{"description":"Arrival airport code of the leg.","type":"string"},"departsAt":{"description":"Local departure date-time of the leg.","type":"string"},"airlines":{"description":"Marketing airline codes on the leg.","type":"array","items":{"type":"string"}}},"required":["legId"],"description":"One leg the options are attributed to, so a legId on an option is legible."}},"options":{"type":"array","items":{"type":"object","properties":{"fareOptionId":{"type":"string","description":"The fare option id (a UUID); sent to wego.com as the booking link's branded_fare param."},"name":{"type":"string","description":"The marketing name, e.g. Economy Lite."},"price":{"type":"object","properties":{"total":{"type":"number","description":"Whole-party total for this fare option in the requested currency, including payment + booking fees. Unlike search-time fares, the fare options read carries a tax figure upstream, surfaced as the sibling totalTaxAmount when present."},"totalUsd":{"type":"number","description":"Whole-party total in USD. The price key options are sorted on WITHIN a leg; the list itself is ordered by leg first."},"currency":{"type":"string","description":"ISO 4217 currency of total."},"totalTaxAmount":{"description":"Whole-party tax for this option, in the same currency as total. Taxes are set by the cabin, not by the fare option, so every fare option in the same cabin carries the same figure while their totals differ. Forwarded when the upstream provides it; absent otherwise.","type":"number"},"passengers":{"description":"Per-passenger-type split of this option's total: one entry per type present in the party, each with its own head count. The party totals sum to total. Absent when the upstream priced the option without a passenger breakdown.","type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["adult","child","infant"],"description":"The passenger type this entry prices."},"count":{"type":"number","description":"How many passengers of this type the party carries."},"perPerson":{"type":"object","properties":{"fare":{"type":"number","description":"Base fare for one passenger of this type."},"tax":{"type":"number","description":"Tax for one passenger of this type."},"total":{"type":"number","description":"What one passenger of this type pays, fare + tax."}},"required":["fare","tax","total"],"description":"What a single passenger of this type pays."},"party":{"type":"object","properties":{"fare":{"type":"number","description":"Base fare for every passenger of this type."},"tax":{"type":"number","description":"Tax for every passenger of this type."},"total":{"type":"number","description":"What every passenger of this type pays together."}},"required":["fare","tax","total"],"description":"perPerson times count, taken from the upstream's own party-level figure when it states one, so it can differ from the exact product in the last decimal place."}},"required":["type","count","perPerson","party"],"description":"One passenger type's share of a fare option, split into base fare and tax."}},"covers":{"description":"How much of the TRIP this total covers: leg = this option's own leg only, so a multi-leg trip needs one option per leg and their sum is the trip price; trip = the whole journey. Always whole-party either way. A positive witness – absent means the upstream did not let us attribute it, never that the total is the whole trip.","type":"string","enum":["leg","trip"]}},"required":["total","totalUsd","currency"],"description":"The price of one fare option (pass-through display values). Always whole-party for passengers; read covers for how much of the trip it pays for."},"refundable":{"type":"boolean","description":"Whether this option is refundable."},"exchangeable":{"type":"boolean","description":"Whether this option allows a date or flight change."},"baggage":{"type":"object","properties":{"cabin":{"description":"Cabin baggage allowance display string.","type":"string"},"checked":{"description":"Checked baggage allowance display string.","type":"string"}},"description":"Baggage allowance display strings for one fare option."},"penalties":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["change","cancel"],"description":"The action the penalty governs."},"policy":{"type":"string","enum":["free","fee","not_permitted"],"description":"free (allowed, no charge), fee (allowed, priced), or not_permitted."},"amount":{"description":"Fee amount, present only when policy is fee.","type":"number"},"currency":{"description":"ISO 4217 currency for amount, present only when policy is fee.","type":"string"}},"required":["type","policy"],"description":"A change or cancel penalty on a fare option."},"description":"Always both change and cancel, in that order."},"termsUrls":{"description":"The airline's terms and conditions links for this option, in the order the carrier lists them. Absent when the carrier publishes its rules as text rather than links, or when the terms read was unavailable - never an empty array. These are the carrier's own pages, not a machine-readable rulebook: refundability, exchangeability, baggage and the change/cancel penalties are the fields on this option, and are what an agent should reason over.","type":"array","items":{"type":"string"}},"legId":{"description":"The leg this option prices, matching a legs[] entry. Present when the upstream attributes it; absent when it does not.","type":"number"}},"required":["fareOptionId","name","price","refundable","exchangeable","baggage","penalties"],"description":"One fare option."},"description":"The full fare option list – no filter, no pagination. Ordered by legId, then cheapest-first inside each leg, so the two legs of a split fare never interleave."},"metadata":{"type":"object","properties":{"currencyCode":{"type":"string","description":"The currency this read ASKED upstream for, and the one every price on it is meant to be in. Read it beside currencyCodeSource before you show a number: a price computed in the wrong currency renders as a perfectly normal price, with no error and no odd shape to notice, so the response states which one rather than leaving it to be inferred. Where the operation also publishes a top-level currencyCode, that field reports the currency the prices actually came back in; the two agree unless upstream declined to reprice."},"currencyCodeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved currencyCode: explicit (the caller sent currency – including a value equal to the default) or default (USD, no currency sent). A default here is the one signal that the request never carried the currency you meant."},"locale":{"type":"string","description":"The language tag this read asked upstream for – what any localized text on it was resolved in (room and board names, airline and airport names, review prose)."},"localeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved locale: explicit (the caller sent locale – including a value equal to the default) or default (en, no locale sent). A default here explains text that came back in a language the caller did not ask for."}},"required":["currencyCode","currencyCodeSource","locale","localeSource"],"description":"What this read resolved currency and locale to, and how each was decided."}},"required":["fareId","currencyCode","options","metadata"],"description":"The fare options for one Book-on-Wego fare, and what this read resolved currency and locale to."}}}},"400":{"description":"Invalid request query/path parameters, or the fares service rejected the fareId or currency (`validation_failed`; the `detail` names the recovery).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The fare was not found or its search has expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream flights service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The flights service is temporarily unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"fareId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}$"},"required":true,"description":"The Book-on-Wego fare id from a flight-search trip detail."},{"in":"query","name":"currency","schema":{"default":"USD","type":"string","pattern":"^[A-Z]{3}$","example":"AED"},"description":"Pricing currency as a 3-letter ISO 4217 code."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35,"example":"en"},"description":"Response language tag."}]}},"/v1/flights/fares/{fareId}/booking-link":{"get":{"operationId":"getFareBookingLink","tags":["Flights"],"summary":"Build a fare's wego.com booking link","description":"Builds the wego.com booking deep-link for a chosen Book-on-Wego fare, with one fare option pre-selected PER LEG. A pure, stateless URL build from the caller-supplied search context - no upstream call, no booking, no payment. The caller passes back the trip/leg/passenger context.","responses":{"200":{"description":"The booking handoff URL.","content":{"application/json":{"schema":{"type":"object","properties":{"bookingUrl":{"type":"string","description":"A wego.com booking deep-link with the chosen fare pre-selected."},"expires":{"type":"boolean","const":true,"description":"Always true: this link is bound to a live search and stops working with it, in about five to seven minutes. To send someone a link that lasts, use GET /v1/flights/search-link."}},"required":["bookingUrl","expires"],"description":"The booking handoff URL, and the fact that it expires."}}}},"400":{"description":"Invalid fare id or query parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"fareId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}$"},"required":true,"description":"The Book-on-Wego fare id from a flight-search trip detail."},{"in":"query","name":"tripId","schema":{"type":"string","maxLength":256,"pattern":"^[A-Za-z0-9._~=-]+:[A-Za-z0-9._:~=-]+$"},"required":true,"description":"The trip the fare belongs to, from a flight-search result. Shaped {searchId}:{tripCode}."},{"in":"query","name":"searchId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}$"},"description":"Optional cross-check. When supplied it must equal the tripId's first :-segment; a mismatch is rejected."},{"in":"query","name":"fareOptionId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}(?:,[A-Za-z0-9._:~=-]{1,256}){0,7}$"},"required":true,"description":"The fare option(s) to pre-select, from GET /v1/flights/fares/{fareId}/options. Required: without it the booking page has no fare to open and dead-ends. When that read reported price.covers=leg, pass ONE id per trip leg as a comma-separated list, in any order: a single id then prices only its own leg while the page still presents the whole round trip. A whole-trip fare (price.covers=trip) takes exactly one id. At most 8, and no id twice."},{"in":"query","name":"from","schema":{"type":"string","pattern":"^[A-Z]{3}$","example":"DXB"},"required":true,"description":"Origin airport or city code, as sent to the flight search."},{"in":"query","name":"to","schema":{"type":"string","pattern":"^[A-Z]{3}$","example":"LHR"},"required":true,"description":"Destination airport or city code, as sent to the flight search."},{"in":"query","name":"fromCity","schema":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["0","1","true","false"]}]},"description":"Whether `from` is a city code rather than an airport code."},{"in":"query","name":"toCity","schema":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["0","1","true","false"]}]},"description":"Whether `to` is a city code rather than an airport code."},{"in":"query","name":"fromDate","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"required":true,"description":"Outbound departure date, YYYY-MM-DD, as sent to the flight search."},{"in":"query","name":"toDate","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"description":"Return date, YYYY-MM-DD. Omit for a one-way handoff."},{"in":"query","name":"cabin","schema":{"default":"economy","type":"string","enum":["economy","premium_economy","business","first"]},"description":"Cabin class, as sent to the flight search."},{"in":"query","name":"adults","schema":{"default":1,"type":"integer","minimum":1,"maximum":9},"description":"Adult passengers (1-9)."},{"in":"query","name":"children","schema":{"default":0,"type":"integer","minimum":0,"maximum":8},"description":"Child passengers (0-8)."},{"in":"query","name":"infants","schema":{"default":0,"type":"integer","minimum":0,"maximum":8},"description":"Infant passengers (0-8). Must not exceed adults."},{"in":"query","name":"siteCode","schema":{"type":"string","pattern":"^[A-Z]{2}$"},"description":"Wego market (point of sale) as a 2-letter code, e.g. AE. Optional: if omitted the API defaults to US. A client that knows the user's market (the wego CLI derives it from the id_token) passes it as an explicit siteCode."},{"in":"query","name":"currency","schema":{"type":"string","pattern":"^[A-Z]{3}$","example":"AED"},"description":"Pricing currency as a 3-letter ISO 4217 code (e.g. AED). Optional: when omitted the built URL carries NO currency parameter – it is not defaulted to USD, so wego.com shows the market's own default. Pass it to pin the handoff to a currency."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35,"example":"en"},"description":"Response language tag for the wego.com page (e.g. en, ar). Defaults to en."}]}},"/v1/flights/search-link":{"get":{"operationId":"getFlightSearchLink","tags":["Flights"],"summary":"Build a durable wego.com search link","description":"Builds a shareable wego.com flight-search URL from the caller's own route, dates, cabin and passengers. A pure, stateless string build - no upstream call, no search created. It carries no search-scoped id, so it does not expire: whoever opens it runs the search live.","responses":{"200":{"description":"The durable wego.com search URL.","content":{"application/json":{"schema":{"type":"object","properties":{"searchUrl":{"type":"string","description":"A wego.com flight-search URL for this route, dates, cabin and passengers. Opening it runs the search live."},"expires":{"type":"boolean","const":false,"description":"Always false: the URL carries no search-scoped id, so it keeps working. The prices behind it are whatever a live search returns when it is opened."}},"required":["searchUrl","expires"],"description":"The durable wego.com search URL."}}}},"400":{"description":"Invalid query parameters. fromDate must be a real calendar date, not in the past, and within 365 days; toDate must be a real date on or after it. A durable link cannot carry a date no live search can represent.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"query","name":"from","schema":{"type":"string","pattern":"^[A-Z]{3}$","example":"DXB"},"required":true,"description":"Origin airport or city code, as sent to the flight search."},{"in":"query","name":"to","schema":{"type":"string","pattern":"^[A-Z]{3}$","example":"LHR"},"required":true,"description":"Destination airport or city code, as sent to the flight search."},{"in":"query","name":"fromCity","schema":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["0","1","true","false"]}]},"description":"Whether `from` is a city code rather than an airport code."},{"in":"query","name":"toCity","schema":{"anyOf":[{"type":"boolean"},{"type":"string","enum":["0","1","true","false"]}]},"description":"Whether `to` is a city code rather than an airport code."},{"in":"query","name":"fromDate","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"required":true,"description":"Outbound departure date, YYYY-MM-DD, as sent to the flight search."},{"in":"query","name":"toDate","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"description":"Return date, YYYY-MM-DD. Omit for a one-way handoff."},{"in":"query","name":"cabin","schema":{"default":"economy","type":"string","enum":["economy","premium_economy","business","first"]},"description":"Cabin class, as sent to the flight search."},{"in":"query","name":"adults","schema":{"default":1,"type":"integer","minimum":1,"maximum":9},"description":"Adult passengers (1-9)."},{"in":"query","name":"children","schema":{"default":0,"type":"integer","minimum":0,"maximum":8},"description":"Child passengers (0-8)."},{"in":"query","name":"infants","schema":{"default":0,"type":"integer","minimum":0,"maximum":8},"description":"Infant passengers (0-8). Must not exceed adults."},{"in":"query","name":"siteCode","schema":{"type":"string","pattern":"^[A-Z]{2}$"},"description":"Wego market (point of sale) as a 2-letter code, e.g. AE. Optional: if omitted the API defaults to US. A client that knows the user's market (the wego CLI derives it from the id_token) passes it as an explicit siteCode."},{"in":"query","name":"currency","schema":{"type":"string","pattern":"^[A-Z]{3}$","example":"AED"},"description":"Pricing currency as a 3-letter ISO 4217 code (e.g. AED). Optional: when omitted the built URL carries NO currency parameter – it is not defaulted to USD, so wego.com shows the market's own default. Pass it to pin the handoff to a currency."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35,"example":"en"},"description":"Response language tag for the wego.com page (e.g. en, ar). Defaults to en."}]}},"/v1/hotels/searches":{"post":{"operationId":"createHotelSearch","tags":["Hotels"],"summary":"Create a hotel search","description":"Creates a Book-on-Wego hotel search (city, single hotel, or geo point) and returns its opaque searchId plus the occupancy priced upstream (resolved child ages, incl. the age-8 fallback when none supplied). Poll /results for ranked hotels. A hotelId search is the only one getHotelRates accepts.","responses":{"201":{"description":"Search created.","content":{"application/json":{"schema":{"type":"object","properties":{"searchId":{"type":"string","description":"Opaque id for the created search."},"occupancy":{"type":"object","properties":{"adults":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Adults priced upstream for this search."},"childrenAges":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"Resolved per-child ages actually sent upstream (age-8 fallback when omitted)."},"rooms":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Rooms priced upstream for this search."}},"required":["adults","childrenAges","rooms"],"description":"The occupancy priced upstream for this search (ages resolved, incl. fallback)."},"siteCode":{"type":"string","description":"The site code (Wego market) the search was created for."},"siteCodeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved siteCode: explicit (caller-supplied – including a market a client derived and passed) or default (US, no site supplied)."}},"required":["searchId","occupancy","siteCode","siteCodeSource"]}}}},"400":{"description":"Invalid request parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Unknown hotel.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream hotels service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The hotels service is unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cityCode":{"type":"string","pattern":"^[A-Z]{3}$","example":"DXB","description":"City code to search. One destination only, see oneOf."},"hotelId":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Search a single hotel by id. One destination only, see oneOf."},"lat":{"type":"number","minimum":-90,"maximum":90,"description":"Latitude. Must be paired with lng."},"lng":{"type":"number","minimum":-180,"maximum":180,"description":"Longitude. Must be paired with lat."},"radius":{"default":10,"description":"Search radius in km around lat/lng.","type":"number","minimum":1,"maximum":50},"checkIn":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Check-in date, YYYY-MM-DD. Not in the past."},"checkOut":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Check-out date, YYYY-MM-DD. Must be after checkIn."},"adults":{"default":2,"description":"Adults across the search (1-9). Defaults to 2, since a room sleeps two. Note the flight search defaults adults to 1.","type":"integer","minimum":1,"maximum":9},"children":{"default":0,"description":"Children across the search (0-8). Defaults to 0.","type":"integer","minimum":0,"maximum":8},"rooms":{"default":1,"description":"Rooms to price (1-4). Defaults to 1; cannot exceed adults.","type":"integer","minimum":1,"maximum":4},"childrenAges":{"description":"Per-child ages (integers 0–17). When provided, the count must equal `children`. When omitted, each child is priced at age 8 (the documented fallback).","maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"currency":{"default":"USD","description":"Pricing currency as a 3-letter ISO 4217 code. Defaults to USD.","type":"string","pattern":"^[A-Z]{3}$"},"locale":{"default":"en","description":"Response language tag (e.g. en, ar). Defaults to en.","type":"string","minLength":1,"maxLength":35},"siteCode":{"type":"string","pattern":"^[A-Z]{2}$","description":"Wego market (point of sale) as a 2-letter code, e.g. AE. Optional: if omitted the API defaults to US. A client that knows the user's market (the wego CLI derives it from the id_token) passes it as an explicit siteCode."}},"required":["checkIn","checkOut"],"additionalProperties":false,"oneOf":[{"required":["cityCode"]},{"required":["hotelId"]},{"required":["lat","lng"]}]}}}}}},"/v1/hotels/searches/{searchId}/results":{"get":{"operationId":"getHotelSearchResults","tags":["Hotels"],"summary":"Read hotel search results","description":"Lean list cards; default 10, max 50 per page. searchComplete:true terminal, false advisory; poll snapshotCandidateCount to a steady non-zero. totalCandidates===0 = filters only if totalBeforeFilters>0, else none bookable once complete. ?refundable=true = witnessed.","responses":{"200":{"description":"Ranked hotels, as lean list cards. Amenities, the full image list, the address and brand/chain are not on a card – read the hotel for the one row you picked.","content":{"application/json":{"schema":{"type":"object","properties":{"searchId":{"type":"string","description":"The id of the search this snapshot belongs to."},"currencyCode":{"type":"string","description":"Currency the prices in this snapshot are quoted in."},"searchComplete":{"type":"boolean","description":"Upstream aggregation flag. true is authoritative/terminal; conclude NO BOOK-ON-WEGO BOOKABLE INVENTORY only when true AND metadata.totalBeforeFilters === 0 – never that no hotel exists, since only Book-on-Wego inventory was requested. A zero totalCandidates on its own means only that this read's filters matched nothing, and an empty page with totalCandidates > 0 is pagination. false is inconclusive, so watch metadata.snapshotCandidateCount convergence to stop sooner."},"stay":{"type":"object","properties":{"checkIn":{"type":"string","description":"Check-in date priced upstream, YYYY-MM-DD."},"checkOut":{"type":"string","description":"Check-out date priced upstream, YYYY-MM-DD."},"nights":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Nights between checkIn and checkOut. price.total covers this many nights of price.amountPerNight."},"occupancy":{"type":"object","properties":{"adults":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Adults priced upstream for this search."},"childrenAges":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"Resolved per-child ages actually sent upstream (age-8 fallback when omitted)."},"rooms":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Rooms priced upstream for this search."}},"required":["adults","childrenAges","rooms"],"description":"The occupancy priced upstream for this search (ages resolved, incl. fallback)."}},"required":["checkIn","checkOut","nights","occupancy"],"description":"What upstream priced: the dates and occupancy every price on this read covers. Present when upstream states them."},"metadata":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"1-based page number of this snapshot."},"pageSize":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Hotels requested per page."},"resultCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Hotels on this page. The page only – judge a filter on totalCandidates."},"totalCandidates":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Hotels matching this read's filters across the snapshot – the count that judges a filter, not the page. A filter that matched nothing is totalCandidates 0 with totalBeforeFilters above 0."},"totalBeforeFilters":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Hotels that survived the Book-on-Wego join, before this read's filters ran. A filter that matched nothing is totalCandidates === 0 with totalBeforeFilters > 0. Zero means no Book-on-Wego-bookable inventory surfaced for these dates – it is NOT proof that no hotel exists, since the join runs over a sampled rate list. Equal to totalCandidates on an unfiltered read."},"filterOptions":{"type":"object","properties":{"amenities":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The term the matching filter query param accepts, verbatim."},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Hotels carrying this value on an unfiltered read. For the substring-matched vocabularies (amenities, propertyTypes, brands, chains, districts) it is a LOWER BOUND, since one term can span several values. For the code-keyed ones (rateTypes, guestTypes) it is EXACT, because those terms are matched exactly rather than as substrings - and a guestTypes count spans only the hotels upstream scored for that cohort, which is fewer than carry an all-guests score."}},"required":["name","count"]},"description":"Amenity terms present in this snapshot, by count."},"propertyTypes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The term the matching filter query param accepts, verbatim."},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Hotels carrying this value on an unfiltered read. For the substring-matched vocabularies (amenities, propertyTypes, brands, chains, districts) it is a LOWER BOUND, since one term can span several values. For the code-keyed ones (rateTypes, guestTypes) it is EXACT, because those terms are matched exactly rather than as substrings - and a guestTypes count spans only the hotels upstream scored for that cohort, which is fewer than carry an all-guests score."}},"required":["name","count"]},"description":"Property-type terms present in this snapshot, by count."},"brands":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The term the matching filter query param accepts, verbatim."},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Hotels carrying this value on an unfiltered read. For the substring-matched vocabularies (amenities, propertyTypes, brands, chains, districts) it is a LOWER BOUND, since one term can span several values. For the code-keyed ones (rateTypes, guestTypes) it is EXACT, because those terms are matched exactly rather than as substrings - and a guestTypes count spans only the hotels upstream scored for that cohort, which is fewer than carry an all-guests score."}},"required":["name","count"]},"description":"Brand terms present in this snapshot, by count."},"chains":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The term the matching filter query param accepts, verbatim."},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Hotels carrying this value on an unfiltered read. For the substring-matched vocabularies (amenities, propertyTypes, brands, chains, districts) it is a LOWER BOUND, since one term can span several values. For the code-keyed ones (rateTypes, guestTypes) it is EXACT, because those terms are matched exactly rather than as substrings - and a guestTypes count spans only the hotels upstream scored for that cohort, which is fewer than carry an all-guests score."}},"required":["name","count"]},"description":"Chain terms present in this snapshot, by count."},"districts":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The term the matching filter query param accepts, verbatim."},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Hotels carrying this value on an unfiltered read. For the substring-matched vocabularies (amenities, propertyTypes, brands, chains, districts) it is a LOWER BOUND, since one term can span several values. For the code-keyed ones (rateTypes, guestTypes) it is EXACT, because those terms are matched exactly rather than as substrings - and a guestTypes count spans only the hotels upstream scored for that cohort, which is fewer than carry an all-guests score."}},"required":["name","count"]},"description":"District terms present in this snapshot, by count."},"rateTypes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The term the matching filter query param accepts, verbatim."},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Hotels carrying this value on an unfiltered read. For the substring-matched vocabularies (amenities, propertyTypes, brands, chains, districts) it is a LOWER BOUND, since one term can span several values. For the code-keyed ones (rateTypes, guestTypes) it is EXACT, because those terms are matched exactly rather than as substrings - and a guestTypes count spans only the hotels upstream scored for that cohort, which is fewer than carry an all-guests score."}},"required":["name","count"]},"description":"Rate types witnessed on this snapshot's Book-on-Wego rates, by hotel count – the vocabulary ?rate-types= accepts (e.g. breakfast_included, free_cancellation). Unlike every other vocabulary here the name is a stable upstream CODE, not a localized display name, so it is matched EXACTLY rather than as a substring and reads the same under any locale. Each count is the number of hotels with at least one rate carrying that type."},"guestTypes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The term the matching filter query param accepts, verbatim."},"count":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Hotels carrying this value on an unfiltered read. For the substring-matched vocabularies (amenities, propertyTypes, brands, chains, districts) it is a LOWER BOUND, since one term can span several values. For the code-keyed ones (rateTypes, guestTypes) it is EXACT, because those terms are matched exactly rather than as substrings - and a guestTypes count spans only the hotels upstream scored for that cohort, which is fewer than carry an all-guests score."}},"required":["name","count"]},"description":"Guest cohorts this snapshot carries ratings for, by hotel count – the vocabulary ?guest-type= accepts (business, couple, family, solo). Like rateTypes the name is a stable CODE matched exactly, not a localized display name, so the counts are precise. Each count is the number of hotels upstream scored for that cohort, which is FEWER than the hotels carrying an all-guests score: upstream publishes no thin cohort rows, so a hotel missing from a cohort's count has too little data for it rather than a poor rating. Spelled differently from the /reviews ?guest-type= vocabulary (family here, family_with_children there) because the two upstreams segment guests differently."},"priceRange":{"type":"object","properties":{"min":{"type":"number","description":"The cheapest hotel's all-in nightly figure, in the response currency."},"max":{"type":"number","description":"The dearest hotel's all-in nightly figure, in the response currency."}},"required":["min","max"],"description":"The span the min-price / max-price bounds compare against, on their own basis: amountPerNight plus every per-night charge the card publishes beside it, localTaxPerNight and taxAmountPerNight where present, covering every room in the search. Both ends are attainable, since the bounds are inclusive, so min-price at min and max-price at max each keep the whole snapshot, and a bound outside the span returns nothing. Read over each hotel's headline price, so with ?refundable=true the bounds move to lowestRefundablePrice and can reach past max. Folded over the same population as totalBeforeFilters, so it does not narrow as other filters bite, and it still moves while searchComplete is false. Absent when the snapshot holds no hotel."}},"required":["amenities","propertyTypes","brands","chains","districts","rateTypes","guestTypes"],"description":"The filterable vocabulary of the hotels in this snapshot, ordered by count, over the same population as totalBeforeFilters. The amenities / property-types / brands / chains / districts query params take an entry's name field VERBATIM (matched case-insensitively as a substring), so pick from here rather than guessing a synonym. A term listed here matches AT LEAST its count on an unfiltered read; the count is a lower bound, since one term can span several values (Pool also matches Indoor Pool). rateTypes and guestTypes are the exceptions: their names are stable codes, matched exactly rather than as substrings, so their counts are precise rather than a lower bound. guestTypes counts a smaller population than the rest for a different reason - it counts only the hotels upstream scored for that cohort, and upstream publishes no thin cohort rows, so a hotel absent from a cohort has too little data for it rather than a poor rating. Still growing while searchComplete is false. priceRange does the same job for the numeric bounds: it states the span min-price / max-price are measured on, so read it before choosing either."},"hasMore":{"type":"boolean","description":"Another page of hotels follows."},"snapshotCandidateCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Upstream aggregation counter – the practical early convergence signal. Two spaced (not back-to-back), equal, non-zero reads ≈ settled enough to render; it stabilizes well before searchComplete flips, so use it to stop polling sooner. A heuristic, not proof of completion (searchComplete:true is that). Not the same as totalCandidates (the post-Book-on-Wego-join hotel count)."},"createdAt":{"description":"When this search was created (ISO 8601) – the freshness anchor for these prices. A search older than about 10 minutes may answer 404 as expired. Absent when the search service omits it.","type":"string"},"currencyCode":{"type":"string","description":"The currency this read ASKED upstream for, and the one every price on it is meant to be in. Read it beside currencyCodeSource before you show a number: a price computed in the wrong currency renders as a perfectly normal price, with no error and no odd shape to notice, so the response states which one rather than leaving it to be inferred. Where the operation also publishes a top-level currencyCode, that field reports the currency the prices actually came back in; the two agree unless upstream declined to reprice."},"currencyCodeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved currencyCode: explicit (the caller sent currency – including a value equal to the default) or default (USD, no currency sent). A default here is the one signal that the request never carried the currency you meant."},"locale":{"type":"string","description":"The language tag this read asked upstream for – what any localized text on it was resolved in (room and board names, airline and airport names, review prose)."},"localeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved locale: explicit (the caller sent locale – including a value equal to the default) or default (en, no locale sent). A default here explains text that came back in a language the caller did not ask for."}},"required":["page","pageSize","resultCount","totalCandidates","totalBeforeFilters","filterOptions","hasMore","snapshotCandidateCount","currencyCode","currencyCodeSource","locale","localeSource"],"description":"Pagination, the snapshot's filter vocabulary, the settle counters for this read, and what it resolved currency and locale to."},"results":{"type":"array","items":{"type":"object","properties":{"hotelId":{"type":"number","description":"The hotel's numeric id; read its detail with GET /v1/hotels/{hotelId}."},"name":{"type":"string","description":"Hotel display name."},"pageUrl":{"type":"string","description":"The hotel's page on wego.com. It is not tied to a search, so it keeps working after this search expires. Give it to a traveller who wants to look at the hotel, and use it in anything that is saved or sent on. It opens with no dates set."},"star":{"description":"Star rating (1-5), when classified.","type":"number"},"review":{"description":"Aggregate guest review score and count.","type":"object","properties":{"score":{"type":"number","description":"Aggregate guest review score (0-10)."},"count":{"type":"number","description":"Number of guest reviews behind the score."}},"required":["score","count"]},"reviewsByGuestType":{"description":"Guest review score and count per cohort, on the same 0-10 scale as review (which is the all-guests figure). These are the exact values ?guest-type= accepts and the same vocabulary metadata.filterOptions.guestTypes counts. A cohort is present only when upstream scored it, and an ABSENT cohort means too little data for that cohort, never a low score - upstream publishes no thin cohort rows. Read these to explain a pick as well as make one: a hotel rated 8.5 overall and 7.6 by families is a different recommendation than one rated 8.5 by both. Omitted when no cohort was scored.","type":"object","properties":{"business":{"description":"How business travellers rate this hotel.","type":"object","properties":{"score":{"type":"number","description":"Guest review score for this cohort (0-10)."},"count":{"type":"number","description":"Number of that cohort's reviews behind the score."}},"required":["score","count"]},"couple":{"description":"How couples rate this hotel.","type":"object","properties":{"score":{"type":"number","description":"Guest review score for this cohort (0-10)."},"count":{"type":"number","description":"Number of that cohort's reviews behind the score."}},"required":["score","count"]},"family":{"description":"How families rate this hotel.","type":"object","properties":{"score":{"type":"number","description":"Guest review score for this cohort (0-10)."},"count":{"type":"number","description":"Number of that cohort's reviews behind the score."}},"required":["score","count"]},"solo":{"description":"How solo travellers rate this hotel.","type":"object","properties":{"score":{"type":"number","description":"Guest review score for this cohort (0-10)."},"count":{"type":"number","description":"Number of that cohort's reviews behind the score."}},"required":["score","count"]}}},"price":{"type":"object","properties":{"scope":{"type":"string","const":"booking","description":"What every amount here covers: the whole booking, all rooms in the search."},"amountPerNight":{"type":"number","description":"One night of the whole booking, in the response currency, covering every room. Excludes localTaxPerNight, and taxAmountPerNight when that field appears. Rounded to a whole currency unit upstream; total is the exact stay figure."},"taxAmountPerNight":{"description":"Per-night tax charged on top of amountPerNight, in the response currency. Present only when the amount excludes it; absent means amountPerNight already covers any such tax, which is the usual case. Add it the same way as localTaxPerNight.","type":"number"},"localTaxPerNight":{"description":"Per-night local tax (city/tourism/municipality), charged on top of amountPerNight. wego.com adds it to the price it displays. Present when upstream reports the tax, where 0 means none charged. Absent when upstream reports it as unknown.","type":"number","minimum":0},"totalLocalTax":{"description":"Stay total of local tax as reported upstream, which rounds the nightly figure. Present when upstream reports the tax. Absent when upstream reports it as unknown.","type":"number","minimum":0},"total":{"description":"Stay total in the response currency. Excludes totalLocalTax, mirroring amountPerNight.","type":"number"},"deal":{"description":"The discount advertised on this rate, absent when the rate carries none. Describes THIS price object, so with ?refundable=true it describes the refundable rate the card switched to.","type":"object","properties":{"label":{"description":"The offer's own tag, e.g. 'Best Deal'. Its presence also says where wasPerNight came from: see that field.","type":"string"},"percentOff":{"type":"integer","minimum":1,"maximum":99,"description":"The offer's discount as a whole percent. A tagged offer's percent wins over a usual-price one, matching the site's precedence, and the two often differ. wego.com renders a percentage only for an UNTAGGED offer; a tagged one it shows as its label plus a crossed-out price, so do not attribute this figure to what the page displays. Always describes the same offer as wasPerNight, so the pair never disagree."},"wasPerNight":{"description":"Pre-discount per-night price, on amountPerNight's basis so the two subtract cleanly. When label is present this is COMPUTED back from the offer's unrounded discount, since a tagged offer carries no pre-discount price of its own and wego.com renders the same computed figure; quote it as approximate, and expect it to differ slightly from a figure you derive using the rounded percentOff. When label is absent it is the quoted pre-discount price itself.","type":"number"},"wasTotal":{"description":"Pre-discount stay total, on total's basis. Computed or quoted on the same rule as wasPerNight.","type":"number"},"promoCode":{"description":"Promo code the traveller enters at checkout, when the offer carries one. Most live offers are provider discounts with no code.","type":"string"}},"required":["percentOff"]},"totalUsd":{"type":"number","description":"Stay total in USD, the cross-currency ranking key."},"currency":{"type":"string","description":"ISO 4217 currency of these amounts."}},"required":["scope","amountPerNight","totalUsd","currency"],"description":"A card's per-night and stay price. Every amount covers the whole booking, all rooms in the search, so quote these figures as they stand. Upstream rounds per booking, so total is the exact stay figure and amountPerNight is one night of it. Any per-night figure published beside amountPerNight is charged ON TOP of it: add localTaxPerNight, and taxAmountPerNight when it appears, to reach what a guest pays and what the price sorts and bounds rank on."},"refundable":{"type":"string","enum":["available","unknown"],"description":"Refundability WITNESS, not a boolean. 'available' = a free-cancellation Book-on-Wego rate was seen in this snapshot. 'unknown' = none was seen, which is NOT evidence that none exists – this envelope carries only a sample of each hotel's rates, so a negative is not computable here. To answer 'does this hotel have a refundable room', read the hotel's rooms/rates."},"lowestRefundablePrice":{"description":"Lowest refundable Book-on-Wego rate. Present exactly when refundable is 'available'.","type":"object","properties":{"scope":{"type":"string","const":"booking","description":"What every amount here covers: the whole booking, all rooms in the search."},"amountPerNight":{"type":"number","description":"One night of the whole booking, in the response currency, covering every room. Excludes localTaxPerNight, and taxAmountPerNight when that field appears. Rounded to a whole currency unit upstream; total is the exact stay figure."},"taxAmountPerNight":{"description":"Per-night tax charged on top of amountPerNight, in the response currency. Present only when the amount excludes it; absent means amountPerNight already covers any such tax, which is the usual case. Add it the same way as localTaxPerNight.","type":"number"},"localTaxPerNight":{"description":"Per-night local tax (city/tourism/municipality), charged on top of amountPerNight. wego.com adds it to the price it displays. Present when upstream reports the tax, where 0 means none charged. Absent when upstream reports it as unknown.","type":"number","minimum":0},"totalLocalTax":{"description":"Stay total of local tax as reported upstream, which rounds the nightly figure. Present when upstream reports the tax. Absent when upstream reports it as unknown.","type":"number","minimum":0},"total":{"description":"Stay total in the response currency. Excludes totalLocalTax, mirroring amountPerNight.","type":"number"},"deal":{"description":"The discount advertised on this rate, absent when the rate carries none. Describes THIS price object, so with ?refundable=true it describes the refundable rate the card switched to.","type":"object","properties":{"label":{"description":"The offer's own tag, e.g. 'Best Deal'. Its presence also says where wasPerNight came from: see that field.","type":"string"},"percentOff":{"type":"integer","minimum":1,"maximum":99,"description":"The offer's discount as a whole percent. A tagged offer's percent wins over a usual-price one, matching the site's precedence, and the two often differ. wego.com renders a percentage only for an UNTAGGED offer; a tagged one it shows as its label plus a crossed-out price, so do not attribute this figure to what the page displays. Always describes the same offer as wasPerNight, so the pair never disagree."},"wasPerNight":{"description":"Pre-discount per-night price, on amountPerNight's basis so the two subtract cleanly. When label is present this is COMPUTED back from the offer's unrounded discount, since a tagged offer carries no pre-discount price of its own and wego.com renders the same computed figure; quote it as approximate, and expect it to differ slightly from a figure you derive using the rounded percentOff. When label is absent it is the quoted pre-discount price itself.","type":"number"},"wasTotal":{"description":"Pre-discount stay total, on total's basis. Computed or quoted on the same rule as wasPerNight.","type":"number"},"promoCode":{"description":"Promo code the traveller enters at checkout, when the offer carries one. Most live offers are provider discounts with no code.","type":"string"}},"required":["percentOff"]},"totalUsd":{"type":"number","description":"Stay total in USD, the cross-currency ranking key."},"currency":{"type":"string","description":"ISO 4217 currency of these amounts."}},"required":["scope","amountPerNight","totalUsd","currency"]},"rateTypes":{"description":"Rate types WITNESSED on this hotel's Book-on-Wego rates in this snapshot (e.g. breakfast_included, free_cancellation), sorted. A witness on the same terms as refundable, never a negative: this envelope carries only a sample of each hotel's rates, so an absent type is not evidence the hotel lacks it. These are the exact values ?rate-types= accepts, and the same vocabulary metadata.filterOptions.rateTypes counts. Omitted when no rate type was witnessed.","type":"array","items":{"type":"string"}},"cityName":{"description":"City the hotel is in.","type":"string"},"districtName":{"description":"District or neighbourhood the hotel is in.","type":"string"},"lat":{"description":"Hotel latitude in decimal degrees. Use with lng to compute distance to any landmark you choose.","type":"number"},"lng":{"description":"Hotel longitude in decimal degrees. Use with lat to compute distance to any landmark you choose.","type":"number"},"distanceToCityCentre":{"description":"Kilometres from the city's place-record coordinate (the point GET /v1/places reports for the city), and the ?sort=distance_asc key. Where a city record covers an island, a city-state or a whole administrative area, that point can sit far from the commercial centre. For locality prefer the districts filter, or compute distance from lat/lng to a landmark you choose. Present when upstream reports it.","type":"number"},"image":{"description":"Primary image URL, if any.","type":"string"},"badges":{"type":"array","items":{"type":"string"},"description":"Full-result-set badges this hotel wins (e.g. cheapest)."}},"required":["hotelId","name","pageUrl","price","refundable","badges"]},"description":"The requested page of ranked hotels, as list cards."}},"required":["searchId","currencyCode","searchComplete","metadata","results"]}}}},"400":{"description":"Invalid request parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Unknown or expired search.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream hotels service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The hotels service is unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"searchId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}$"},"required":true,"description":"The opaque searchId returned by createHotelSearch. Ids expire; a 404 means the search is unknown or gone – create a new one."},{"in":"query","name":"page","schema":{"default":1,"type":"integer","minimum":1,"maximum":100},"description":"Page number, 1-based (max 100). Defaults to 1."},{"in":"query","name":"pageSize","schema":{"default":10,"type":"integer","minimum":1,"maximum":50},"description":"Results per page (1-50). Defaults to 10."},{"in":"query","name":"sort","schema":{"default":"relevance","type":"string","enum":["relevance","price_asc","price_desc","star_desc","review_score_desc","guest_rating_desc","distance_asc"]},"description":"Sort order. relevance (default) is the metasearch ranking; price_asc / price_desc by cheapest per-night rate; star_desc by star; review_score_desc by guest score; guest_rating_desc by the ?guest-type= cohort's own score, which requires that param (a hotel upstream did not score for the cohort sorts last, never zero-filled); distance_asc by distance to the city's place-record coordinate (see distanceToCityCentre). With ?refundable=true the price sorts key off the cheapest refundable rate."},{"in":"query","name":"currency","schema":{"default":"USD","type":"string","pattern":"^[A-Z]{3}$"},"description":"Pricing currency as a 3-letter ISO 4217 code (e.g. AED). Defaults to USD."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35},"description":"Response language tag (e.g. en, ar). Defaults to en."},{"in":"query","name":"min-star","schema":{"type":"integer","minimum":1,"maximum":5},"description":"Keep hotels with at least this star rating (1-5)."},{"in":"query","name":"max-star","schema":{"type":"integer","minimum":1,"maximum":5},"description":"Keep hotels with at most this star rating (1-5)."},{"in":"query","name":"min-review-score","schema":{"type":"number","minimum":0,"maximum":10},"description":"Keep hotels whose ALL-GUESTS review score is at least this (0-10). This is the everybody-rated-it-well question; for a named guest cohort use ?guest-type= with ?min-guest-rating=, which is a different number on most hotels."},{"in":"query","name":"guest-type","schema":{"type":"string","enum":["business","couple","family","solo"]},"description":"The guest cohort ?min-guest-rating= and sort=guest_rating_desc judge a hotel by: business, couple, family or solo. Read the vocabulary and this snapshot's per-cohort hotel counts from metadata.filterOptions.guestTypes. Must be sent with ?min-guest-rating= or sort=guest_rating_desc, and both of those require it - a cohort with nothing to apply it to is rejected rather than silently ignored. Spelled differently from the /reviews ?guest-type= vocabulary (family here, family_with_children there) because the two upstreams segment guests differently; business exists only here and extended_group only there."},{"in":"query","name":"min-guest-rating","schema":{"type":"number","minimum":0,"maximum":10},"description":"Keep hotels the ?guest-type= cohort rates at least this (0-10). Requires ?guest-type=. Judged on that cohort's own score, not the all-guests one: on a settled 420-hotel snapshot, of the 299 hotels scored for both, 58% had a family score at least 3 points from their overall one. A hotel upstream did not score for the cohort is DROPPED, and that absence means too little cohort data rather than a low score - upstream publishes no thin cohort rows, so a cohort score it does publish rests on more reviews than the all-guests figure sometimes does."},{"in":"query","name":"min-price","schema":{"type":"number","minimum":0,"maximum":1000000},"description":"Minimum price (inclusive), in the response currency. Bounds the all-in nightly figure: amountPerNight plus every per-night charge the card publishes beside it, localTaxPerNight and taxAmountPerNight where present. That figure covers every room in the search, so multiply a per-room budget by the room count in stay.occupancy.rooms. Read metadata.filterOptions.priceRange for the bounds this snapshot spans. With ?refundable=true it bounds the cheapest refundable rate."},{"in":"query","name":"max-price","schema":{"type":"number","minimum":0,"maximum":1000000},"description":"Maximum price (inclusive), in the response currency. Bounds the all-in nightly figure: amountPerNight plus every per-night charge the card publishes beside it, localTaxPerNight and taxAmountPerNight where present. That figure covers every room in the search, so multiply a per-room budget by the room count in stay.occupancy.rooms. Read metadata.filterOptions.priceRange for the bounds this snapshot spans. With ?refundable=true it bounds the cheapest refundable rate."},{"in":"query","name":"refundable","schema":{"type":"string","enum":["0","1","true","false"]},"description":"Keep only hotels with a witnessed refundable Book-on-Wego rate, so a 'cheapest refundable' answer needs no per-hotel /rates calls. Exactly equivalent to ?rate-types=free_cancellation, and combines with it: this is the one rate type that has its own param. This is an UNDER-approximation: the results envelope is a rate sample, so a true keeps hotels with a seen refundable rate and a hotel's absence is not authoritative – only GET /v1/hotels/{hotelId}/rates can prove a hotel has no refundable rate. Accepts true or false."},{"in":"query","name":"rate-types","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Keep hotels with a witnessed Book-on-Wego rate carrying ALL listed rate types (AND across terms; repeat or comma-separate), so 'only rooms with breakfast' needs no per-hotel /rates calls. Read the vocabulary from metadata.filterOptions.rateTypes (e.g. breakfast_included, free_cancellation) – unlike every other list filter these are matched EXACTLY (case-insensitively), not as substrings, because they are stable upstream codes rather than localized display names. Several terms mean ONE rate carrying all of them, and while any rate-type filter is active the card price, the price bounds, the price sorts and the cheapest badge all key off that matching rate. Like refundable, an UNDER-approximation: the results envelope is a rate sample, so a hotel's absence is not proof it lacks the type – only GET /v1/hotels/{hotelId}/rates can prove that."},{"in":"query","name":"deals-only","schema":{"type":"string","enum":["0","1","true","false"]},"description":"Keep only hotels whose card carries a price.deal, mirroring the 'today's deals' filter on wego.com. Judged on the very price object the card publishes, so the kept hotels and the deals shown always agree – with ?refundable=true that means the refundable rate must be the discounted one. Like refundable, this is an UNDER-approximation: the results envelope is a rate sample that grows while searchComplete is false, so a hotel's absence is not proof it has no discount. Accepts true or false."},{"in":"query","name":"amenities","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Keep hotels offering ALL listed amenities (AND across terms; repeat or comma-separate). Each term is matched case-insensitively as a substring against the name field in metadata.filterOptions.amenities – pick terms from there (e.g. Fitness Centre), not a guessed synonym (gym). One term can span several values (Pool also matches Indoor Pool)."},{"in":"query","name":"property-types","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Keep hotels whose property type matches ANY listed term (OR; repeat or comma-separate). Matched case-insensitively as a substring against the name field in metadata.filterOptions.propertyTypes – pick from there rather than guessing."},{"in":"query","name":"brands","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Keep hotels whose brand matches ANY listed term (OR; repeat or comma-separate). Matched case-insensitively as a substring against the name field in metadata.filterOptions.brands – pick from there rather than guessing. Names parent companies as well as individual brands, and a term matches an entry name rather than a corporate relationship, so a group filed under several sibling brands needs each of those names listed."},{"in":"query","name":"chains","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Keep hotels whose chain matches ANY listed term (OR; repeat or comma-separate). Matched case-insensitively as a substring against the name field in metadata.filterOptions.chains – pick from there rather than guessing. Most hotels carry no chain and some entries name a loyalty programme, so a hotel group may be reachable only through brands, or split across both vocabularies."},{"in":"query","name":"districts","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Keep hotels whose district matches ANY listed term (OR; repeat or comma-separate). Matched case-insensitively as a substring against the name field in metadata.filterOptions.districts – pick from there rather than guessing."},{"in":"query","name":"view","schema":{"default":"card","type":"string","enum":["card"]},"description":"Response projection. `card` is the only value: the lean results-list projection (price summary, refundability witness, star/review, location names). The former `default` projection was removed in issue #1308 – read GET /v1/hotels/{hotelId} for a hotel's amenities, images and address."}]}},"/v1/hotels/{hotelId}/rates/{rateId}/booking-link":{"get":{"operationId":"getHotelRateBookingLink","tags":["Hotels"],"summary":"Build a hotel booking link","description":"Returns the wego.com checkout URL for a chosen rate. Pure build: no upstream call, no booking, no payment – only 400/401/429.","responses":{"200":{"description":"The checkout URL, and the fact that it expires.","content":{"application/json":{"schema":{"type":"object","properties":{"bookingUrl":{"type":"string","description":"The wego.com hotel checkout URL for the rate."},"expires":{"type":"boolean","const":true,"description":"Always true: this link is search-scoped and stops working when the rate's search expires. A stale link loads an empty checkout page rather than erroring, so treat it as short-lived and re-price the rate to get a fresh one. To send someone a link that lasts, use the hotel's pageUrl."}},"required":["bookingUrl","expires"]}}}},"400":{"description":"Invalid request parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"hotelId","schema":{"type":"integer","minimum":1,"maximum":9007199254740991},"required":true,"description":"The hotel's numeric id (a positive integer), as carried by hotel search results (results[].hotelId) and embedded in a rate id."},{"in":"path","name":"rateId","schema":{"type":"string"},"required":true,"description":"The rate's composed booking reference from GET /v1/hotels/{hotelId}/rates (rates[].id), forwarded verbatim. Grammar: {searchId}:hotels.wego.com:{hotelId}:{hash}:{idx} – checkout derives the search from the first segment, which is why the searchId query param is optional here. Opaque: do not construct or reorder it."},{"in":"query","name":"searchId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}$"},"description":"Optional. The search the rate belongs to. When omitted it defaults to the rate id's first segment (exactly how the checkout page recovers it), so you rarely need to send it; when sent it must equal that segment or the request is rejected 400."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35},"description":"Checkout page language tag (e.g. en, ar). Defaults to en."},{"in":"query","name":"guests","schema":{"type":"string","maxLength":64,"pattern":"^[1-9]\\d*(:\\d+)*$"},"description":"Optional packed occupancy token forwarded to checkout as guests. Grammar ^[1-9][0-9]*(:[0-9]+)*$: the first segment is the adult count (>= 1), then one child age per following :-segment – e.g. 2:4:9 is 2 adults plus children aged 4 and 9. The checkout-URL twin of the create body's adults / childrenAges."},{"in":"query","name":"countryCode","schema":{"type":"string","pattern":"^[A-Z]{2}$"},"description":"Optional ISO 3166-1 alpha-2 code forwarded verbatim to checkout as its country_code (the guest's booking country). It does NOT pick the wego.com host – that is siteCode. Send countryCode for the traveller's country, siteCode for the Wego market/domain."},{"in":"query","name":"siteCode","schema":{"type":"string","pattern":"^[A-Z]{2}$"},"description":"Wego market (point of sale) as a 2-letter code, e.g. AE; defaults to US. It selects the wego.com CHECKOUT HOST/domain – distinct from countryCode, which is passed through to the page as the guest's booking country and does not change the host."}]}},"/v1/hotels/search-link":{"get":{"operationId":"getHotelSearchLink","tags":["Hotels"],"summary":"Build a durable wego.com hotel search link","description":"Builds a shareable wego.com hotel-search URL from the caller's own city, dates and occupancy. A pure, stateless string build: no search created. It carries no search-scoped id, so it does not expire: whoever opens it runs the search live.","responses":{"200":{"description":"The durable wego.com hotel-search URL.","content":{"application/json":{"schema":{"type":"object","properties":{"searchUrl":{"type":"string","description":"A wego.com hotel-search URL for this city, dates and occupancy. Opening it runs the search live."},"expires":{"type":"boolean","const":false,"description":"Always false: the URL carries no search-scoped id, so it keeps working. The prices behind it are whatever a live search returns when it is opened."}},"required":["searchUrl","expires"],"description":"The durable wego.com hotel-search URL."}}}},"400":{"description":"Invalid query parameters. checkIn must be a real calendar date and not in the past; checkOut must be after it; rooms is 1-4 and cannot be more than adults; childrenAges is required when children is above 0 and must have exactly that many entries.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"query","name":"cityCode","schema":{"type":"string","pattern":"^[A-Z]{3}$","example":"BKK"},"required":true,"description":"City code the link searches, e.g. BKK. Take it from a places result's code or cityCode, never its numeric id. A lat/lng pair cannot be shared: wego.com serves no coordinate search URL. To link one hotel instead of a search, use the pageUrl that hotel carries."},{"in":"query","name":"checkIn","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"required":true,"description":"Check-in date, YYYY-MM-DD. Not in the past."},{"in":"query","name":"checkOut","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"required":true,"description":"Check-out date, YYYY-MM-DD. Must be after checkIn."},{"in":"query","name":"adults","schema":{"default":2,"type":"integer","minimum":1,"maximum":9},"description":"Adults across the link's rooms (1-9). Defaults to 2."},{"in":"query","name":"children","schema":{"default":0,"type":"integer","minimum":0,"maximum":8},"description":"Children across the link's rooms (0-8). Defaults to 0. Sending more than 0 requires childrenAges."},{"in":"query","name":"childrenAges","schema":{"type":"string","pattern":"^\\d{1,2}(,\\d{1,2})*$"},"description":"Per-child ages as a comma-separated list of integers 0-17, e.g. 5,9. The count must equal children, and it is required whenever children is above 0: the create body prices a missing age at 8, and a durable link would show that guess to a recipient who cannot correct it."},{"in":"query","name":"rooms","schema":{"default":1,"type":"integer","minimum":1,"maximum":4},"description":"Rooms the link asks for (1-4). Defaults to 1, and cannot be more than adults. Guests spread evenly and fill the earlier rooms first, so 3 adults in 2 rooms give 2 then 1, matching the way a search prices the same stay."},{"in":"query","name":"currency","schema":{"type":"string","pattern":"^[A-Z]{3}$"},"description":"Optional pricing currency as a 3-letter ISO 4217 code. When omitted the page prices in whatever the recipient's own session uses."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35},"description":"Page language tag (e.g. en, ar). Defaults to en."},{"in":"query","name":"siteCode","schema":{"type":"string","pattern":"^[A-Z]{2}$"},"description":"Wego market (point of sale) as a 2-letter code, e.g. AE; defaults to US. It selects the wego.com host the link points at."}]}},"/v1/hotels/{hotelId}/rates":{"get":{"operationId":"getHotelRates","tags":["Hotels"],"summary":"List a hotel's rooms & rates","description":"Returns the Book-on-Wego rooms & rates for a hotel (cheapest-first): room name, board, refundability, price, and each rate's composed booking reference id. searchId must name a hotel-scoped search (one created with hotelId); a city or geo search is a 409.","responses":{"200":{"description":"Cheapest-first Book-on-Wego rates.","content":{"application/json":{"schema":{"type":"object","properties":{"hotelId":{"type":"number","description":"The hotel these rates are for."},"searchId":{"type":"string","description":"The search these rates were priced within."},"currencyCode":{"type":"string","description":"The currency every rate price on this read was computed in – the same value as metadata.currencyCode, which carries currencyCodeSource beside it. Read it before you show a number: a price computed in the wrong currency renders as a perfectly normal price."},"searchComplete":{"type":"boolean","description":"Advisory: true means upstream reports it finished aggregating rates for this search. It is not a guarantee that the list on this page is final, so do not block on it."},"stay":{"type":"object","properties":{"checkIn":{"type":"string","description":"Check-in date priced upstream, YYYY-MM-DD."},"checkOut":{"type":"string","description":"Check-out date priced upstream, YYYY-MM-DD."},"nights":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Nights between checkIn and checkOut. price.total covers this many nights of price.amountPerNight."},"occupancy":{"type":"object","properties":{"adults":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Adults priced upstream for this search."},"childrenAges":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"Resolved per-child ages actually sent upstream (age-8 fallback when omitted)."},"rooms":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Rooms priced upstream for this search."}},"required":["adults","childrenAges","rooms"],"description":"The occupancy priced upstream for this search (ages resolved, incl. fallback)."}},"required":["checkIn","checkOut","nights","occupancy"],"description":"What upstream priced: the dates and occupancy every price on this read covers. Present when upstream states them."},"rates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Composed booking reference (opaque passthrough); pass it to GET /v1/hotels/{hotelId}/rates/{rateId}/booking-link."},"roomName":{"type":"string","description":"Room type name, e.g. Deluxe King."},"board":{"description":"Board basis, normalized to lower_snake_case (e.g. room_only, breakfast_included). Absent when the provider states none.","type":"string"},"refundable":{"type":"boolean","description":"True when this rate carries a refundable or free-cancellation code. Authoritative on this endpoint, unlike the results card's refundable witness."},"cancellationPolicy":{"description":"Coarse policy derived from the refundability codes: free_cancellation or non_refundable.","type":"string"},"price":{"type":"object","properties":{"scope":{"type":"string","const":"booking","description":"What every amount here covers: the whole booking, all rooms in the search."},"amountPerNight":{"type":"number","description":"One night of the whole booking, in the request currency, covering every room. Rounded to a whole currency unit upstream; total is the exact stay figure."},"taxAmountPerNight":{"description":"Per-night tax, when reported.","type":"number"},"taxInclusive":{"description":"Whether amountPerNight already includes taxAmountPerNight. Says nothing about localTaxPerNight, which is excluded either way.","type":"boolean"},"localTaxPerNight":{"description":"Per-night local tax (city / tourism / municipality), charged on top of amountPerNight whatever taxInclusive says. wego.com quotes amountPerNight + localTaxPerNight, so quote both. Present when upstream reports the tax, where 0 means none charged. Absent when upstream reports it as unknown.","type":"number","minimum":0},"totalLocalTax":{"description":"Stay total of localTaxPerNight as reported upstream, which rounds the nightly figure. Present when upstream reports the tax. Absent when upstream reports it as unknown.","type":"number","minimum":0},"total":{"description":"Stay total in the request currency. Excludes totalLocalTax, mirroring amountPerNight.","type":"number"},"totalUsd":{"type":"number","description":"Stay total in USD – the cross-currency sort key."},"currency":{"type":"string","description":"ISO 4217 currency of the amounts on this price."},"deal":{"description":"The discount advertised on this room's rate, absent when it carries none. Rooms commonly share one offer, so treat a deal here as a property of the rate rather than as a rare find, and compare percentOff across the rooms before recommending one.","type":"object","properties":{"label":{"description":"The offer's own tag, e.g. 'Best Deal'. Its presence also says where wasPerNight came from: see that field.","type":"string"},"percentOff":{"type":"integer","minimum":1,"maximum":99,"description":"This room's discount as a whole percent. A tagged offer's percent wins over a usual-price one, matching the site's precedence, and the two often differ. wego.com renders a percentage only for an UNTAGGED offer; a tagged one it shows as its label plus a crossed-out price, so do not attribute this figure to what the page displays. Always describes the same offer as wasPerNight, so the pair never disagree."},"wasPerNight":{"description":"Pre-discount per-night price, on amountPerNight's basis so the two subtract cleanly. When label is present this is COMPUTED back from the offer's unrounded discount, since a tagged offer carries no pre-discount price of its own and wego.com renders the same computed figure; quote it as approximate, and expect it to differ slightly from a figure you derive using the rounded percentOff. When label is absent it is the quoted pre-discount price itself.","type":"number"},"wasTotal":{"description":"Pre-discount stay total, on total's basis. Computed or quoted on the same rule as wasPerNight.","type":"number"},"promoCode":{"description":"Promo code the traveller enters at checkout, when the offer carries one. Most live offers are provider discounts with no code. Scoped to this room's rate; wego.com instead shows one such code above the whole room list.","type":"string"}},"required":["percentOff"]}},"required":["scope","amountPerNight","totalUsd","currency"],"description":"A rate's pricing. Every amount covers the whole booking, all rooms in the search, so quote these figures as they stand. Upstream rounds per booking, so total is the exact stay figure and amountPerNight is one night of it. All amounts exclude totalLocalTax, which wego.com adds to the displayed price."},"roomsLeft":{"description":"Rooms remaining at this rate, when the provider reports scarcity; absent otherwise.","type":"number"},"images":{"description":"Room image URLs, when the provider supplies them.","type":"array","items":{"type":"string"}}},"required":["id","roomName","refundable","price"]},"description":"Bookable rates for the hotel in this search."},"metadata":{"type":"object","properties":{"currencyCode":{"type":"string","description":"The currency this read ASKED upstream for, and the one every price on it is meant to be in. Read it beside currencyCodeSource before you show a number: a price computed in the wrong currency renders as a perfectly normal price, with no error and no odd shape to notice, so the response states which one rather than leaving it to be inferred. Where the operation also publishes a top-level currencyCode, that field reports the currency the prices actually came back in; the two agree unless upstream declined to reprice."},"currencyCodeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved currencyCode: explicit (the caller sent currency – including a value equal to the default) or default (USD, no currency sent). A default here is the one signal that the request never carried the currency you meant."},"locale":{"type":"string","description":"The language tag this read asked upstream for – what any localized text on it was resolved in (room and board names, airline and airport names, review prose)."},"localeSource":{"type":"string","enum":["explicit","default"],"description":"How the API resolved locale: explicit (the caller sent locale – including a value equal to the default) or default (en, no locale sent). A default here explains text that came back in a language the caller did not ask for."}},"required":["currencyCode","currencyCodeSource","locale","localeSource"],"description":"What this read resolved currency and locale to, and how each was decided."}},"required":["hotelId","searchId","currencyCode","searchComplete","rates","metadata"]}}}},"400":{"description":"Invalid request parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Unknown hotel, or unknown/expired search.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The searchId names a city or geo search, which never holds a hotel's full rate list. Create a hotel-scoped search (createHotelSearch with hotelId) and read its rates.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream hotels service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The hotels service is unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"hotelId","schema":{"type":"integer","minimum":1,"maximum":9007199254740991},"required":true,"description":"The hotel's numeric id (a positive integer), as carried by hotel search results (results[].hotelId) and embedded in a rate id."},{"in":"query","name":"searchId","schema":{"type":"string","pattern":"^[A-Za-z0-9._:~=-]{1,256}$"},"required":true,"description":"The search the hotel was found in (from createHotelSearch); rates are priced within that search context. Required."},{"in":"query","name":"currency","schema":{"default":"USD","type":"string","pattern":"^[A-Z]{3}$"},"description":"Pricing currency as a 3-letter ISO 4217 code (e.g. AED). Defaults to USD."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35},"description":"Response language tag (e.g. en, ar). Defaults to en."}]}},"/v1/hotels/{hotelId}/reviews":{"get":{"operationId":"getHotelReviews","tags":["Hotels"],"summary":"Search a hotel's guest reviews","description":"Guest reviews for a hotel, newest first: rating, pros, cons and the provider. Filter by topic with ?topics=breakfast,pool and by cohort with ?guest-type=. Quote a review against metadata.totalCandidates, and cite metadata.matchedTerms for the word actually matched.","responses":{"200":{"description":"One page of guest reviews.","content":{"application/json":{"schema":{"type":"object","properties":{"hotelId":{"type":"number","description":"The hotel these reviews are for."},"metadata":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"1-based page number of this read."},"pageSize":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Reviews requested per page."},"resultCount":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Reviews on this page."},"totalCandidates":{"description":"Reviews matching this read's filters across ALL pages – the denominator to quote a review against ('15 of 141'). It is the FILTERED total, so an unfiltered read is needed to state the hotel's full review count. EXACT only when hasMore is false: when hasMore is true this can be a LOWER BOUND, because an upstream page that carries no count of its own falls back to the offset plus the rows it sent, so quote it as 'at least N'. ABSENT when this read establishes no total at all – an empty page past the first whose upstream sent no count says nothing about the pages before it. Absent means UNKNOWN, never zero: re-read page 1 before reporting any number.","type":"integer","minimum":0,"maximum":9007199254740991},"hasMore":{"type":"boolean","description":"Another page may follow. A full page always sets this, count or no count. While it is true, read totalCandidates as a floor rather than a corpus size."},"topics":{"type":"array","items":{"type":"string"},"description":"The topic terms this read asked for."},"matchedTerms":{"type":"array","items":{"type":"string"},"description":"The term variants the upstream actually matched (e.g. breakfast, Breakfast). Empty on an unfiltered read. Cite from here rather than from topics, so a quote states the word that was really found."}},"required":["page","pageSize","resultCount","hasMore","topics","matchedTerms"],"description":"Pagination and the matched-topic accounting for this reviews read."},"results":{"type":"array","items":{"type":"object","properties":{"rating":{"type":"number","description":"The provider's own 0–10 rating for this review, passed through."},"title":{"description":"Review title, when the guest gave one.","type":"string"},"postedAt":{"type":"string","description":"Calendar date (YYYY-MM-DD)."},"providerCode":{"type":"string","description":"Which provider collected the review (e.g. booking.com)."},"guestType":{"description":"The reviewer's cohort, normalized onto the closed set. Omitted when the upstream sent a value outside it.","type":"string","enum":["couple","family_with_children","solo_traveller","extended_group"]},"pros":{"type":"array","items":{"type":"string"},"description":"What the guest liked, verbatim."},"cons":{"type":"array","items":{"type":"string"},"description":"What the guest disliked, verbatim."},"notes":{"description":"view=detail only – review prose the provider tagged neither positive nor negative.","type":"array","items":{"type":"string"}},"countryCode":{"description":"view=detail only – the reviewer's country code. The reviewer's name is never returned.","type":"string"}},"required":["rating","postedAt","providerCode","pros","cons"]},"description":"The requested page of guest reviews."}},"required":["hotelId","metadata","results"]}}}},"400":{"description":"Invalid request parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Unknown hotel.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream hotels service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The hotels service is unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"hotelId","schema":{"type":"integer","minimum":1,"maximum":9007199254740991},"required":true,"description":"The hotel's numeric id (a positive integer), as carried by hotel search results (results[].hotelId) and embedded in a rate id."},{"in":"query","name":"page","schema":{"default":1,"type":"integer","minimum":1,"maximum":100},"description":"Page number, 1-based (max 100). Defaults to 1."},{"in":"query","name":"pageSize","schema":{"default":10,"type":"integer","minimum":1,"maximum":50},"description":"Reviews per page (1-50). Defaults to 10."},{"in":"query","name":"sort","schema":{"default":"posted_at_desc","type":"string","enum":["posted_at_desc","rating_desc","rating_asc"]},"description":"Sort order. posted_at_desc (default, newest first) – a review corpus answers 'what is it like now', so recency opens; rating_desc / rating_asc sort by the provider's rating."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35},"description":"Response language tag (e.g. en, ar). Defaults to en."},{"in":"query","name":"topics","schema":{"minItems":1,"type":"array","items":{"type":"string"}},"description":"Optional topic terms to filter reviews by (repeat or comma-separate, OR'd together) – e.g. ?topics=breakfast,pool keeps reviews mentioning either. metadata.matchedTerms reports the variants actually matched (breakfast, Breakfast)."},{"in":"query","name":"guest-type","schema":{"type":"string","enum":["couple","family_with_children","solo_traveller","extended_group"]},"description":"Optional reviewer-cohort filter: couple, family_with_children, solo_traveller or extended_group."},{"in":"query","name":"view","schema":{"default":"default","type":"string","enum":["default","detail"]},"description":"Response projection. default: rating, title, pros, cons, provider. detail: adds the reviewer's country code and neutral prose notes. Defaults to default."}]}},"/v1/hotels/{hotelId}":{"get":{"operationId":"getHotel","tags":["Hotels"],"summary":"Get hotel detail","description":"Returns static hotel detail (name, stars, address, images, amenities, reviews). Use ?view=detail for the richer UI projection.","responses":{"200":{"description":"Hotel detail (agent default) or the detail projection.","content":{"application/json":{"schema":{"type":"object","properties":{"hotelId":{"type":"number","description":"The hotel's numeric id."},"name":{"type":"string","description":"Hotel display name."},"pageUrl":{"type":"string","description":"The hotel's page on wego.com. It is not tied to a search, so it keeps working after this search expires. Give it to a traveller who wants to look at the hotel, and use it in anything that is saved or sent on. It opens with no dates set."},"star":{"description":"Star rating (1-5), when classified.","type":"number"},"review":{"type":"object","properties":{"score":{"type":"number","description":"Aggregate guest review score (0-10)."},"count":{"type":"number","description":"Number of guest reviews behind the score."}},"required":["score","count"],"description":"Aggregate guest review score and count."},"location":{"type":"object","properties":{"lat":{"description":"Latitude in decimal degrees.","type":"number"},"lng":{"description":"Longitude in decimal degrees.","type":"number"},"address":{"description":"Street address, when available.","type":"string"},"cityName":{"description":"City name.","type":"string"},"districtName":{"description":"District or neighbourhood name.","type":"string"},"countryName":{"description":"Country name.","type":"string"}},"description":"Where the hotel is: coordinates and place names."},"propertyType":{"description":"Property type, e.g. Hotel, Apartment.","type":"string"},"brandName":{"description":"Brand name, when the hotel belongs to one.","type":"string"},"chainName":{"description":"Parent chain name, when known.","type":"string"},"description":{"description":"Editorial description of the hotel.","type":"string"},"amenities":{"description":"Hotel-level amenity names.","type":"array","items":{"type":"string"}},"images":{"description":"Hotel image URLs.","type":"array","items":{"type":"string"}},"distanceToCityCentre":{"description":"Kilometres from the city's place-record coordinate (the point GET /v1/places reports for the city). Where a city record covers an island, a city-state or a whole administrative area, that point can sit far from the commercial centre. For a specific landmark, compute distance from location.lat/lng. Present when upstream reports it.","type":"number"},"distanceToNearestAirport":{"description":"Distance to the nearest airport, as the upstream reports it (unit not normalized here). Absent when upstream omits it.","type":"number"},"categorizedImages":{"description":"view=detail only – hotel images grouped by category (e.g. Rooms, Pool). Absent when the content service supplies none.","type":"array","items":{"type":"object","properties":{"category":{"type":"string","description":"Image category name, e.g. Rooms, Pool."},"images":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","description":"Image URL."},"altText":{"description":"Alt text for the image, when provided.","type":"string"}},"required":["url"]},"description":"Images in this category."}},"required":["category","images"]}},"reviewHighlights":{"description":"view=detail only – short editorial review snippets with a sentiment tag. Absent when none are published.","type":"array","items":{"type":"object","properties":{"sentiment":{"description":"Sentiment tag for the snippet, e.g. positive, negative.","type":"string"},"text":{"type":"string","description":"The review snippet text."}},"required":["text"]}},"highlights":{"description":"Editorial badges from the hotel content service.","type":"array","items":{"type":"object","properties":{"text":{"type":"string","description":"Badge label."},"subtext":{"description":"Badge supporting text, when present.","type":"string"}},"required":["text"]}}},"required":["hotelId","name","pageUrl","location"]}}}},"400":{"description":"Invalid request parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Unknown hotel.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded; retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The upstream hotels service returned an invalid response.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The hotels service is unavailable (`upstream_unavailable`) or rate-limited upstream (`upstream_rate_limited`); retry after the `Retry-After` seconds.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"in":"path","name":"hotelId","schema":{"type":"integer","minimum":1,"maximum":9007199254740991},"required":true,"description":"The hotel's numeric id (a positive integer), as carried by hotel search results (results[].hotelId) and embedded in a rate id."},{"in":"query","name":"locale","schema":{"default":"en","type":"string","minLength":1,"maxLength":35},"description":"Response language tag (e.g. en, ar). Defaults to en."},{"in":"query","name":"view","schema":{"default":"default","type":"string","enum":["default","detail"]},"description":"Response projection. default: the agent hotel detail. detail: the richer UI projection (categorized images, review highlights, badges). Defaults to default."}]}}}}