{"openapi":"3.1.0","info":{"title":"directrate API","version":"1.0.0","description":"Live brand-direct hotel rates & award availability across 11 chains. Cash + member + points, per request, from the chains' own systems."},"servers":[{"url":"https://api.directrate.dev/v1","description":"production"}],"security":[{"apiKey":[]}],"tags":[{"name":"rates","description":"Live cash + member rates"},{"name":"award","description":"Points / award availability"},{"name":"search","description":"Typeahead, geo-search, property metadata"},{"name":"account","description":"Supported chains & usage"}],"paths":{"/shop":{"post":{"tags":["rates"],"summary":"Shop one hotel × dates","operationId":"shop","description":"Room × rate-plan matrix (cash + member, and points where the chain returns them) for one hotel and stay — every rate plan. Prices come back in the chain's own tax basis (`tax_included` says which) plus `price_per_night_after_tax` for cross-chain comparison — see the pricing reference in PRICING.md. Billed the chain's shop weight per live shop (see /chains shop_credits); cache hits 0.25×; errors free. max_age (sec) caps cache staleness; max_age=0 forces live.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/award":{"post":{"tags":["award"],"summary":"Award / points for one hotel × dates","operationId":"award","description":"Same request as /shop; rates become award_rates with points, plus a cash_comparison and cpp (cents-per-point). Billed like /shop (shop weight live, 0.25× cached). max_age (sec) caps cache staleness; max_age=0 forces live.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AwardResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/parity":{"post":{"tags":["parity"],"summary":"Direct rates beside each OTA's, for one hotel and stay","operationId":"parity","description":"One hotel of a chain × checkin × los: the direct public rate, member rate and points (POST /shop) beside the same property's lowest public and member (Genius / Member Price) rates on Booking.com, Expedia, Hotels.com and Trip.com (POST /shop with chain=<ota> and the hotel's id there), the cheapest channel, each channel's gap to direct and the deepest undercut. Ids come from the id map (GET /hotels/{chain}/{code}/ids), resolved live when missing (`resolve`, default on), or from `ids` in the request. Compared per night after tax wherever both sides carry it; `comparison_note` says when a side is native. Channels run in parallel; one failing channel is reported in its row (`skipped`). Billed as the shops it made (each chain's weight, cached 0.25×) plus 1 credit when ids were resolved live — PRICING.md §14.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParityRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParityResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/hotels/{chain}/{code}/ids":{"get":{"tags":["parity"],"summary":"The hotel's ids on Google, Booking.com, Expedia / Hotels.com and Trip.com","operationId":"hotelIds","description":"The id map: a chain hotel code → the same property's Google place id and its hotel id on each OTA (the `hotel_code` to pass with chain=<ota> elsewhere). Served from the map; sources not mapped yet are resolved live unless resolve=0: first the hotel's Google Hotels entity page, found by name + city, whose partner links name the Booking, Expedia / Hotels.com, Trip.com and Agoda ids at once (Google's own cross-source match, confidence 0.85; the entity id is kept under google_hotels); then, for channels it did not list, the source's own typeahead with the candidates' coordinates checked against the hotels master (0.9 within 1 km, 0.5 name-only or 1–3 km). Ids can also be set by hand (admin) or passed on /parity. Google is asked politely: at most one page every 3 s across the service, a name it cannot match is not asked again for 7 days, and after its rate-limit page the resolver parks for 30 min (the typeaheads carry on). 0 credits from the map, 1 credit when a live resolution ran.","parameters":[{"name":"chain","in":"path","required":true,"schema":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]}},{"name":"code","in":"path","required":true,"schema":{"type":"string"}},{"name":"resolve","in":"query","schema":{"type":"integer","enum":[0,1],"default":1}},{"name":"sources","in":"query","schema":{"type":"string"},"description":"comma-separated subset of google, google_hotels, booking, expedia, hotelscom, ctrip, agoda"}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HotelIdsResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/hotels/ids/jobs":{"post":{"tags":["parity"],"summary":"Resolve the id map for many hotels as a paced background job","operationId":"idsJob","description":"Up to 500 hotel codes of one chain → one job that resolves each hotel's ids (Google Hotels first, then the sources' own typeaheads) at one hotel per six seconds, parked while Google is cooling down after a rate-limit page, with a webhook when done. Poll GET /jobs/{id} (kind ids; tasks_done = hotels processed, cells_done = ids found); read the results from GET /hotels/{chain}/{code}/ids. Billed 1 credit per hotel that needed a live resolution, as the job runs; the worst case (1 per hotel) is checked against the quota at creation. The way to fill the map for a city or a compset without a burst.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["chain","hotel_codes"],"properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_codes":{"type":"array","items":{"type":"string"},"maxItems":500},"sources":{"type":"array","items":{"type":"string","enum":["google","google_hotels","booking","expedia","hotelscom","ctrip","agoda"]}},"webhook_url":{"type":"string","format":"uri"}}}}}},"responses":{"202":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/ota/{source}/shop":{"post":{"tags":["parity"],"summary":"One OTA's room × rate-plan matrix for a hotel and stay, raw bodies on request","operationId":"otaShop","description":"Exactly POST /shop with chain=<source> (booking, expedia, hotelscom, ctrip, agoda; hotel_code = the hotel's id on that source), plus `raw: true` to get the source's upstream bodies beside the normalized answer (forces a live call). Every OTA rate is tax-inclusive on the comparable field: Booking price_per_night = the displayed gross (VAT in, property fees out) and after_tax = all-in; Expedia / Hotels.com = the tax-inclusive nightly; Trip.com price_per_night = pre-tax room price (tax_included false) and after_tax = all-in; Agoda answers in USD only and says per answer whether its figure is tax-inclusive (tax_included), filling after_tax only then. A Genius / Member Price / Member deal rate is member_only. Billed as /shop for the source.","parameters":[{"name":"source","in":"path","required":true,"schema":{"type":"string","enum":["booking","expedia","hotelscom","ctrip","agoda"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtaShopRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtaShopResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/stay":{"post":{"tags":["rates"],"summary":"A multi-night stay: the stay rate, each night alone, and the split verdict","operationId":"stay","description":"One hotel × checkin × los (2–14): the stay shopped as one (as POST /shop at los — `stay.lowest` is the nightly figure, averaged where the chain prices nights differently) and every night of it priced on its own (as POST /calendar at los 1 — `nights`, with min_los where the chain states one). `totals` puts stay × los against the nights added up (public, member, after-tax; same chain, same basis); `split` says which is cheaper, the saving, and whether a night-by-night booking is even possible (every night available alone with no minimum stay above 1), naming the nights that block it. Billed as the two calls would be: the shop weight (cached 0.25×) plus the calendar nights (0.1 per night on native-calendar chains, the shop weight per live night elsewhere), under one usage line `stay`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StayResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/calendar":{"post":{"tags":["rates"],"summary":"Lowest rate per night over a bounded window","operationId":"calendar","description":"One hotel × up to 60 forward nights → lowest cash/member (+points) per night. Live or fresh-cache (shares the /shop cache both ways). Billed the shop weight per LIVE night; cached nights 0.25×. max_age (sec) caps the staleness of every night served from cache (judged by the oldest night); max_age=0 shops every night live.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/compset":{"post":{"tags":["rates"],"summary":"Lowest rate for up to 25 hotels","operationId":"compset","description":"Many hotels × one stay → lowest per hotel (summary only; for every rate plan use /shop). `lowest.bar` is always the lowest PUBLIC rate; member rates stay in `lowest.member`. Native batch where the chain has one, else a paced fan-out. Billed per hotel returned at the chain's compset rate (see /chains compset_hotel_credits; hotels served from cache 0.25×; error/not_found free), minimum 1 credit per call. rates: \"lowest\" (default, fewest upstream calls — public lowest, plus the member rate where the chain returns it for free) | \"member\" (guarantees the lowest member rate; add-on credits per hotel only where it needs extra upstream calls, see /chains compset_extras). max_age (sec) caps cache staleness; max_age=0 forces live.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompsetRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompsetResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/compset/award":{"post":{"tags":["award"],"summary":"Lowest points for up to 25 hotels","operationId":"compsetAward","description":"Many hotels × one stay → lowest points + cpp per hotel. Billed like /compset (compset rate per hotel returned, cached 0.25×, minimum 1); chains without award points return empty rows at 0 credit.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompsetRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompsetAwardResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/matrix":{"post":{"tags":["rates"],"summary":"Hotels × nights × los → cells, now or as a job","operationId":"matrix","description":"Up to 200 hotels × 366 checkin nights × 3 lengths of stay (≤ 20,000 cells), kind cash (lowest cash per cell) or award (lowest points per cell; refused on chains without off-device points). Cells the store already holds within max_age (default: the rate cache TTL) are returned at once at the cache-hit rate; the rest becomes a job on the batch queue, quoted at its worst case against your quota, and the response is 202 with `job`. Poll GET /jobs/{id}; page the cells with GET /jobs/{id}/cells. Cash cells on chains with a native batch endpoint cost the compset per-hotel rate; everything else the chain's shop weight.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixResponse"}}}},"202":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/jobs/{id}":{"get":{"tags":["rates"],"summary":"Progress of a matrix job","operationId":"job","description":"Status, cells done, tasks done/failed, credits reserved vs spent. 0 credits. A job is `done` once every task is accounted for and at least one succeeded (`tasks_failed` says how partial), `failed` only if none did.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/jobs/{id}/cells":{"get":{"tags":["rates"],"summary":"The cells a job has filled, paged","operationId":"jobCells","description":"Cells in hotel, night, los order. Pass `next_cursor` back as `cursor`; `limit` up to 1000 (default 200). 0 credits — the job already paid for them.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":1000,"default":200}}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCellsResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/export":{"post":{"tags":["rates"],"summary":"A /matrix selection as a CSV or JSON-lines file","operationId":"export","description":"The /matrix request plus `format` (csv, the default, or jsonl). Cells the store holds within max_age come back as the file (200; text/csv or application/x-ndjson, Content-Disposition attachment), billed exactly as /matrix bills served cells (X-Credits-Charged on the file). When the store cannot serve every cell, the same job is queued and the answer is the JSON MatrixResponse with 202 and X-Job-Id — fetch the file from GET /jobs/{id}/export once it is done. Nothing is stored server-side; a file is re-derived from the store on each fetch.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportRequest"}}}},"responses":{"200":{"description":"the file","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"text/csv":{"schema":{"type":"string"}},"application/x-ndjson":{"schema":{"type":"string"}}}},"202":{"description":"not all in the store: the job","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/jobs/{id}/export":{"get":{"tags":["rates"],"summary":"A job's cells as a CSV or JSON-lines file","operationId":"jobExport","description":"Every cell of the job in hotel, night, los order as one file (`format` csv or jsonl), streamed. 0 credits — the job paid. Columns (csv): chain, hotel_code, night, los, kind, adults, children, rooms, currency, status, min_los, lowest_bar, lowest_bar_after_tax, lowest_member, lowest_points, lowest_points_cash, cash_copay, cpp, cpp_points_cash, certs (cert:top_up;…), from_default_occupancy, observed_at, cache_age_sec. X-Job-Status says whether the job had finished when the file was made.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","schema":{"type":"string","enum":["csv","jsonl"],"default":"csv"}}],"responses":{"200":{"description":"the file","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"text/csv":{"schema":{"type":"string"}},"application/x-ndjson":{"schema":{"type":"string"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/area":{"post":{"tags":["award"],"summary":"Every hotel near a place, priced for one stay, ranked","operationId":"area","description":"Resolves the place exactly as GET /hotels does (cached 24 h), takes the nearest max_hotels (≤ 200), and prices them for checkin × los exactly as POST /matrix does: cells the store holds within max_age come back now, ranked by lowest points (kind award) or lowest bar (kind cash); the rest becomes a job (202, poll GET /jobs/{id}, page GET /jobs/{id}/cells). Billed as the hotels lookup plus the served cells; the job carries its own reserve.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AreaRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AreaResponse"}}}},"202":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AreaResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/award/search":{"post":{"tags":["award"],"summary":"Award nights over a date range, many hotels, ranked","operationId":"awardSearch","description":"The flexible-dates award search: a place (q, place_id or lat+lng, radius in miles) × up to 62 checkin nights × the chains you name (default: every chain with award points and a geo-search) → hotels ranked by cheapest matching night (or best cpp, or most matching nights), each with its matching nights listed; `cert` (+ `max_top_up`) keeps only nights a free-night certificate would cover (Marriott 35k/40k/50k/85k, Hilton, IHG 40k — see MatrixCell.certs). Answered from the cell store, which the chains' award calendars fill a year at a time: cells within max_age (default 6 h) are served; hotels with no cell in the window get a /matrix fill job per chain (fill=missing, the default; fill=stale also refreshes old cells; fill=none never fills) and the answer is 202 with `jobs` — re-run the search when they finish. Billed: the per-chain place lookups as GET /hotels, plus the LISTED nights of the RETURNED hotels at the chain's cache-hit award rate per night, capped at 1 credit per hotel, floor 1 credit per search; scanned-but-unlisted nights are free; fill jobs reserve their own worst case. See PRICING.md §9.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AwardSearchRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AwardSearchResponse"}}}},"202":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AwardSearchResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/calendar/area":{"post":{"tags":["award"],"summary":"City heatmap: per night, how many hotels are open and the cheapest figure","operationId":"calendarArea","description":"One chain's hotels near a place (q, place_id or lat+lng; radius in miles; the nearest max_hotels, default 50) × up to 62 checkin nights → one row per night with hotels_known, hotels_available, the cheapest figure (copay-free points for kind award, the public bar for kind cash), its hotel, the median, and for award the best cpp; plus each hotel's cheapest night. Read from the cell store within max_age (default 6 h award, 1 h cash); hotels with no cell in the window get one /matrix fill job (fill=missing, the default; stale also refreshes old cells; none never) and the answer is 202 with `job` — re-run when it finishes. Billed: the place lookup as GET /hotels, plus per night min(hotels_available × the chain's cache-hit night rate, 1), floor 1 credit; empty nights and unknown hotel-nights are free; the fill job reserves its own worst case. PRICING.md §12.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarAreaRequest"}}}},"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarAreaResponse"}}}},"202":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarAreaResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/award/value":{"get":{"tags":["award"],"summary":"Sweet-spot table: hotels by best cents-per-point over a window","operationId":"awardValue","description":"Hotels ranked by the best cpp their copay-free award nights reach over `from`..`to` (default: the next 60 nights, max 93), read straight from the cell store — no place lookup, no chain call, no fill. Narrow with `chains` (comma-separated), `country` and/or `city` (from the hotels master), `min_cpp`, `min_nights`; `rank=avg` orders by mean cpp instead of the single best night. Each row carries its best night in full (points, cash legs, certs, observed_at). Default occupancy only. Freshness: `max_age` default 7 days — editorial content, not a booking check; verify a night with /award before booking. Billed one award night-unit per hotel returned (0.025 on calendar chains), floor 1 credit — PRICING.md §11.","parameters":[{"name":"chains","in":"query","schema":{"type":"string"},"description":"comma-separated chain ids"},{"name":"country","in":"query","schema":{"type":"string"}},{"name":"city","in":"query","schema":{"type":"string"}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}},{"name":"currency","in":"query","schema":{"type":"string","default":"USD"}},{"name":"min_cpp","in":"query","schema":{"type":"number"}},{"name":"min_nights","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"rank","in":"query","schema":{"type":"string","enum":["best","avg"],"default":"best"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"max_age","in":"query","schema":{"type":"integer","minimum":0,"default":604800}}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AwardValueResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/watches":{"post":{"tags":["award"],"summary":"Watch nights for a change and get a signed webhook","operationId":"watchCreate","description":"One hotel × up to 90 checkin nights × one los, refreshed every `refresh_sec` (900 s to 86400 s). condition `available` fires when a night becomes available, `points_lte` when lowest points drop to the threshold (kind award), `any` on every change, `rebook` when the direct rate (kind cash: lowest.bar, or bar_after_tax / member per `rebook`) or the award (kind award: lowest_points) drops under what the guest paid by at least `rebook.min_saving_pct` — the rebooking alert, one stay per watch (nights.from == to, los = the stay), the event carrying `rebook.{paid,current,saving,saving_pct,saving_total}`; `undercut` (kind cash, a hotel chain) when the OTA channel named in `undercut` (channel + the hotel's id there, from GET /hotels/{chain}/{code}/ids) sells the same nights under the direct public rate by at least `undercut.min_pct`, after tax by default — the parity alert; the channel's cells join every refresh (and credits_per_refresh), and the event carries `undercut.{direct,channel_rate,diff,diff_pct,basis}` — each on the transition, once. Deliveries are POSTed to webhook_url with `X-Directrate-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, t + '.' + body)>` and retried for ~24 h; missed events can be replayed from GET /events. Billed per refresh at credits_per_refresh (cells × per-cell price × the cache-hit multiplier — a refresh is shared with every other watch on the same nights). The secret is returned once, here.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchRequest"}}}},"responses":{"201":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"tags":["award"],"summary":"List this key's watches","operationId":"watchList","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"watches":{"type":"array","items":{"$ref":"#/components/schemas/WatchResponse"}}}}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/watches/{id}":{"get":{"tags":["award"],"summary":"One watch","operationId":"watchGet","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"tags":["award"],"summary":"Delete a watch","operationId":"watchDelete","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"deleted"},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/watches/{id}/pause":{"post":{"tags":["award"],"summary":"Pause a watch (no refreshes, no billing)","operationId":"watchPause","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/watches/{id}/resume":{"post":{"tags":["award"],"summary":"Resume a paused watch; it refreshes at the next sweep","operationId":"watchResume","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/events":{"get":{"tags":["award"],"summary":"Replay watch events","operationId":"events","description":"Every event on this key's watches in creation order, delivered or not — the body is exactly what was (or will be) POSTed to the webhook, plus delivered_at and attempts. `since` (ISO-8601) and `cursor` page forward; `limit` up to 500 (default 100). 0 credits.","parameters":[{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/suggest":{"get":{"tags":["search"],"summary":"Typeahead → places/hotels","operationId":"suggest","description":"1 credit (cached 0.25×).","parameters":[{"name":"chain","in":"query","required":true,"schema":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]}},{"name":"q","in":"query","required":true,"schema":{"type":"string"}},{"name":"country","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/hotels":{"get":{"tags":["search"],"summary":"Geo-search → hotel codes + geo metadata","operationId":"hotels","description":"1 credit on plane-A chains; plane-B chains (wyndham, bestwestern) bill their shop weight. Cached 0.25×.","parameters":[{"name":"chain","in":"query","required":true,"schema":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]}},{"name":"q","in":"query","schema":{"type":"string"},"description":"free-text place (or use place_id, or lat+lng)"},{"name":"place_id","in":"query","schema":{"type":"string"}},{"name":"lat","in":"query","schema":{"type":"number"}},{"name":"lng","in":"query","schema":{"type":"number"}},{"name":"radius","in":"query","schema":{"type":"number","minimum":1,"maximum":100,"default":30},"description":"miles"},{"name":"country","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HotelsResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/hotel":{"get":{"tags":["search"],"summary":"Static property metadata for one hotel","operationId":"hotel","description":"Name/brand/address/geo/rating, plus amenities/photos/description where a details source is wired. Served from cache; 0 credit unless a live details fetch runs (1 credit, then cached).","parameters":[{"name":"chain","in":"query","required":true,"schema":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]}},{"name":"code","in":"query","required":true,"schema":{"type":"string"}},{"name":"refresh","in":"query","schema":{"type":"string","enum":["1"]},"description":"force a fresh details fetch"}],"responses":{"200":{"description":"OK","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HotelDetailResponse"}}}},"400":{"description":"invalid_request","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"auth_failed — missing or invalid x-api-key","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"rate_limited or quota_exceeded (Retry-After set on rate limits)","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"source_unavailable — upstream down; not billed","headers":{"X-Credits-Charged":{"description":"Credits billed for THIS request — the same number written to usage. 0 on errors, on not_found, and on free metadata reads.","schema":{"type":"number"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/chains":{"get":{"tags":["account"],"summary":"Supported chains & per-chain capabilities","operationId":"chains","responses":{"200":{"description":"chains[] with plane, credits, and which endpoints are wired","content":{"application/json":{"schema":{"type":"object","properties":{"chains":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"plane":{"type":"string","enum":["A","B"]},"enabled":{"type":"boolean"},"shop_credits":{"type":"number"},"compset_mode":{"type":"string","enum":["batch","fanout"]},"compset_hotel_credits":{"type":"number"},"compset_extras":{"type":"object","properties":{"member":{"type":"number"}}},"award":{"type":"boolean"},"calendar":{"type":"boolean"},"suggest":{"type":"boolean"},"hotels":{"type":"boolean"},"details":{"type":"boolean"}}}}}}}}}}}},"/usage":{"get":{"tags":["account"],"summary":"Credit usage & quota for the key","operationId":"usage","description":"The account's credit position this period. A quota belongs to the ACCOUNT: every key of the same account draws on one allowance, so `credits_used` is the account's spend and `credits_used_this_key` is this key's share of it. Free to call, and still answered when the quota is exhausted.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string"},"period":{"type":"string"},"credits_used":{"type":"number"},"credits_used_this_key":{"type":"number"},"credits_limit":{"type":"number","nullable":true},"credits_remaining":{"type":"number","nullable":true},"day":{"type":"string"},"credits_used_today":{"type":"number"},"credits_used_today_this_key":{"type":"number"},"daily_credits_limit":{"type":"number","nullable":true},"daily_credits_remaining":{"type":"number","nullable":true},"rate_limit_rps":{"type":"number"}}}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"ShopRequest":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_code":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$"},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"los":{"default":1,"type":"integer","minimum":1,"maximum":30},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"rate_filters":{"type":"array","items":{"type":"string","enum":["bar","member","advance_purchase","package","corporate","aaa","senior","government","other"]}},"max_age":{"description":"Max acceptable cache age in seconds. Omitted: any cached answer within the cache TTL, at the cached rate. 0: always shop live, at the live rate. Not part of the cache key.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["chain","hotel_code","checkin"]},"ShopResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"currency":{"nullable":true,"type":"string"}},"required":["code"],"additionalProperties":false},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"]},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"rates":{"type":"array","items":{"type":"object","properties":{"room_code":{"type":"string"},"room_name":{"nullable":true,"type":"string"},"bed_type":{"nullable":true,"type":"string"},"max_persons":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rate_code":{"nullable":true,"type":"string"},"rate_name":{"nullable":true,"type":"string"},"rate_type":{"default":"other","type":"string","enum":["bar","member","advance_purchase","package","corporate","aaa","senior","government","other"]},"member_only":{"default":false,"type":"boolean"},"refundable":{"nullable":true,"type":"boolean"},"cancel_deadline":{"nullable":true,"type":"string"},"meal_plan":{"default":"room_only","type":"string","enum":["room_only","breakfast","half_board","full_board","all_inclusive"]},"price_per_night":{"description":"Nightly rate in this chain's NATIVE tax basis — read `tax_included` to know which. Averaged over the stay when nightly rates differ. Not comparable across chains; use price_per_night_after_tax for that.","nullable":true,"type":"number"},"price_per_night_after_tax":{"description":"Nightly rate INCLUDING taxes and fees — the cross-chain comparable figure. Derived per chain from source fields verified against live payloads. Null when the source carries no tax data (never estimated).","nullable":true,"type":"number"},"price_total":{"description":"Price for the WHOLE stay (los nights), not per night.","nullable":true,"type":"number"},"taxes_fees_total":{"description":"Taxes + fees for the whole stay, when the chain itemises them. Null otherwise.","nullable":true,"type":"number"},"tax_included":{"description":"Whether price_per_night already includes taxes/fees. Measured per chain: true for hyatt, ihg, melia, premierinn, radisson; false for hilton, marriott, choice, bestwestern, iprefer, wyndham.","nullable":true,"type":"boolean"},"currency":{"default":"USD","type":"string"},"rooms_left":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"points_per_night":{"description":"Award cost in points PER NIGHT (not the whole stay).","nullable":true,"type":"number"},"points_total":{"description":"Award cost in points for the whole stay (points_per_night x los when the source gives only one of the two).","nullable":true,"type":"number"},"cash_copay":{"description":"Cash required PER NIGHT on top of the points, for a points+cash award. A positive value means this is NOT a free award night.","nullable":true,"type":"number"}},"required":["room_code","rate_type","member_only","meal_plan","currency"],"additionalProperties":false}},"lowest":{"default":{},"description":"Lowest NIGHTLY figures across `rates`. Keys: `bar` (lowest public rate), `member` (lowest member-only rate), `bar_after_tax` and `member_after_tax` (the same two on an after-tax basis). Compare across chains with the *_after_tax keys only — `bar`/`member` are in each chain's native tax basis. A key is absent when no rate supplies it; zero/negative placeholder prices are excluded.","type":"object","additionalProperties":{"type":"number"}},"shopped_at":{"type":"string"},"source":{"default":"live","type":"string","enum":["live","cached"]},"cache_age_sec":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["chain","hotel","checkin","los","occupancy","status","rates","lowest","shopped_at","source","cache_age_sec"],"additionalProperties":false},"AwardResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"currency":{"nullable":true,"type":"string"}},"required":["code"],"additionalProperties":false},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"]},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"award_rates":{"type":"array","items":{"type":"object","properties":{"room_code":{"type":"string"},"room_name":{"nullable":true,"type":"string"},"rate_code":{"nullable":true,"type":"string"},"rate_name":{"nullable":true,"type":"string"},"award_type":{"default":"standard","type":"string","enum":["standard","premium","points_cash","off_peak","peak","free_night_cert"]},"points_per_night":{"description":"Points PER NIGHT for this award.","nullable":true,"type":"number"},"points_total":{"description":"Points for the whole stay (points_per_night x los).","nullable":true,"type":"number"},"cash_copay":{"description":"Cash PER NIGHT required alongside the points (points+cash awards). Positive -> not a free night.","nullable":true,"type":"number"},"taxes_fees":{"description":"Mandatory cash taxes/fees on an award stay, where the chain states them. Most US chains charge none on a standard award; resort fees and EU/UK tax may still apply.","nullable":true,"type":"number"},"currency":{"nullable":true,"type":"string"},"rooms_left":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["room_code","award_type"],"additionalProperties":false}},"cash_comparison":{"nullable":true,"type":"object","properties":{"lowest_cash":{"description":"Lowest purchasable nightly cash rate for the same stay, in the chain's native tax basis (identical to /shop lowest.bar). Award rows are excluded — their cash figure is only the award night's tax/fee part.","nullable":true,"type":"number"},"lowest_cash_after_tax":{"description":"The same rate on an after-tax basis (= /shop lowest.bar_after_tax). This is the cash leg `cpp` uses; null on chains that publish no tax data, where cpp falls back to lowest_cash.","nullable":true,"type":"number"},"member_rate":{"description":"Lowest member-only nightly cash rate, native tax basis. Null when the chain returns none for an anonymous shop.","nullable":true,"type":"number"},"currency":{"type":"string"},"fx_to_usd":{"description":"USD per unit of `currency` on the day, used to put cpp in US cents (1 for USD). Null when the currency is unknown to the rate table — cpp is then null too. Source: ECB reference rates, refreshed daily.","nullable":true,"type":"number"}},"required":["currency"],"additionalProperties":false},"cpp":{"description":"Redemption value of the COPAY-FREE award in US CENTS PER POINT, per night: cash × fx_to_usd / lowest_points × 100, where `cash` is cash_comparison.lowest_cash_after_tax (falling back to lowest_cash on chains with no tax data) and fx_to_usd converts a hotel quoted in another currency. Null when either leg or the rate is unknown.","nullable":true,"type":"number"},"lowest_points":{"description":"Lowest COPAY-FREE award per night (standard free night) — the same figure the chain's award calendar reports.","nullable":true,"type":"number"},"lowest_points_cash":{"description":"Points per night of the cheapest POINTS+CASH award, when one is sold; `cash_copay` is its co-pay.","nullable":true,"type":"number"},"cash_copay":{"description":"Cash per night alongside `lowest_points_cash`; null when there is no points+cash award.","nullable":true,"type":"number"},"cpp_points_cash":{"description":"Cents per point of the points+cash award: (cash − cash_copay) / lowest_points_cash × 100; 0 when the co-pay meets or exceeds the cash rate.","nullable":true,"type":"number"},"certs":{"description":"Present when non-empty: the free-night certificates that would cover this stay's copay-free award per night (`lowest_points`), cheapest first, each with the top-up it needs — as MatrixCell.certs. Judged at read time from program terms.","type":"array","items":{"type":"object","properties":{"cert":{"type":"string","enum":["marriott_35k","marriott_40k","marriott_50k","marriott_85k","hilton_free_night","ihg_40k"]},"top_up":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Points the member adds on top of the certificate for this night; 0 = the certificate alone."}},"required":["cert","top_up"],"additionalProperties":false}},"shopped_at":{"type":"string"},"source":{"default":"live","type":"string","enum":["live","cached"]},"cache_age_sec":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["chain","hotel","checkin","los","occupancy","status","award_rates","shopped_at","source","cache_age_sec"],"additionalProperties":false},"CalendarRequest":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_code":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$"},"start":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"days":{"default":30,"type":"integer","minimum":1,"maximum":60},"los":{"default":1,"type":"integer","minimum":1,"maximum":7},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"max_age":{"description":"Max acceptable cache age in seconds. Omitted: any cached answer within the cache TTL, at the cached rate. 0: always shop live, at the live rate. Not part of the cache key.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["chain","hotel_code","start"]},"CalendarResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"currency":{"nullable":true,"type":"string"}},"required":["code"],"additionalProperties":false},"start":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"days":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"currency":{"type":"string"},"nights":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"]},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"lowest":{"default":{},"description":"Lowest nightly figures for this single night — same keys as ShopResponse.lowest (`bar`, `member`, `bar_after_tax`, `member_after_tax`).","type":"object","additionalProperties":{"type":"number"}},"lowest_points":{"description":"Lowest COPAY-FREE award for this night (the standard free night). Points+cash awards never lower it.","nullable":true,"type":"number"},"currency":{"nullable":true,"type":"string"},"source":{"type":"string","enum":["live","cached"]},"cache_age_sec":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["date","status","lowest","source","cache_age_sec"],"additionalProperties":false}},"shopped_at":{"type":"string"},"source":{"default":"live","type":"string","enum":["live","cached"]},"cache_age_sec":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["chain","hotel","start","days","los","occupancy","currency","nights","shopped_at","source","cache_age_sec"],"additionalProperties":false},"CompsetRequest":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_codes":{"minItems":1,"maxItems":25,"type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$"}},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"los":{"default":1,"type":"integer","minimum":1,"maximum":30},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"max_age":{"description":"Max acceptable cache age in seconds. Omitted: any cached answer within the cache TTL, at the cached rate. 0: always shop live, at the live rate. Not part of the cache key.","type":"integer","minimum":0,"maximum":9007199254740991},"rates":{"default":"lowest","type":"string","enum":["lowest","member"]}},"required":["chain","hotel_codes","checkin"]},"CompsetResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"hotels":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"]},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"lowest":{"default":{},"description":"Lowest nightly figures for this hotel — same keys as ShopResponse.lowest (`bar`, `member`, `bar_after_tax`, `member_after_tax`). `bar` is always the lowest PUBLIC rate; member-only rates appear under `member` and never collapse into `bar`.","type":"object","additionalProperties":{"type":"number"}},"lowest_points":{"description":"Lowest COPAY-FREE award (standard free night). Points+cash awards never lower it.","nullable":true,"type":"number"},"currency":{"nullable":true,"type":"string"}},"required":["code","status","lowest"],"additionalProperties":false}},"shopped_at":{"type":"string"},"source":{"default":"live","type":"string","enum":["live","cached"]},"cache_age_sec":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["chain","checkin","los","occupancy","hotels","shopped_at","source","cache_age_sec"],"additionalProperties":false},"CompsetAwardResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"hotels":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"],"description":"available when ANY award is sold — read lowest_points for a copay-free night and lowest_points_cash for a points+cash one; a row can be available with lowest_points null when only points+cash is on sale."},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"lowest_points":{"description":"Lowest COPAY-FREE award per night (the standard free night) — the figure every chain's award calendar reports, so it compares across sources.","nullable":true,"type":"number"},"lowest_points_cash":{"description":"Points per night of the cheapest POINTS+CASH award, when the chain sells one; its co-pay is `cash_copay`.","nullable":true,"type":"number"},"cash_copay":{"description":"Cash per night alongside `lowest_points_cash`. Null when there is no points+cash award.","nullable":true,"type":"number"},"lowest_cash":{"description":"Lowest purchasable nightly cash rate at this hotel, native tax basis (award rows excluded).","nullable":true,"type":"number"},"lowest_cash_after_tax":{"description":"The same rate after tax — the cash leg of `cpp`. Null on chains that publish no tax data.","nullable":true,"type":"number"},"cpp":{"description":"Cents per point of the COPAY-FREE award: lowest_cash_after_tax (or lowest_cash) / lowest_points × 100.","nullable":true,"type":"number"},"cpp_points_cash":{"description":"Cents per point of the points+cash award: (cash − cash_copay) / lowest_points_cash × 100; 0 when the co-pay meets the cash rate.","nullable":true,"type":"number"},"currency":{"nullable":true,"type":"string"},"certs":{"description":"Present when non-empty: the free-night certificates that would cover `lowest_points`, as AwardResponse.certs.","type":"array","items":{"type":"object","properties":{"cert":{"type":"string","enum":["marriott_35k","marriott_40k","marriott_50k","marriott_85k","hilton_free_night","ihg_40k"]},"top_up":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Points the member adds on top of the certificate for this night; 0 = the certificate alone."}},"required":["cert","top_up"],"additionalProperties":false}}},"required":["code","status"],"additionalProperties":false}},"award_supported":{"type":"boolean"},"shopped_at":{"type":"string"},"source":{"default":"live","type":"string","enum":["live","cached"]},"cache_age_sec":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["chain","checkin","los","occupancy","hotels","award_supported","shopped_at","source","cache_age_sec"],"additionalProperties":false},"SuggestResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"query":{"type":"string"},"supported":{"type":"boolean"},"suggestions":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["hotel","place"]},"name":{"type":"string"},"code":{"nullable":true,"type":"string"},"place_id":{"nullable":true,"type":"string"},"place_type":{"nullable":true,"type":"string"},"city":{"nullable":true,"type":"string"},"region":{"nullable":true,"type":"string"},"country":{"nullable":true,"type":"string"},"hotel_codes":{"nullable":true,"type":"array","items":{"type":"string"}}},"required":["type","name"],"additionalProperties":false}},"shopped_at":{"type":"string"},"source":{"default":"live","type":"string","enum":["live","cached"]},"cache_age_sec":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["chain","query","supported","suggestions","shopped_at","source","cache_age_sec"],"additionalProperties":false},"HotelsResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"supported":{"type":"boolean"},"center":{"nullable":true,"type":"object","properties":{"label":{"nullable":true,"type":"string"},"latitude":{"nullable":true,"type":"number"},"longitude":{"nullable":true,"type":"number"}},"additionalProperties":false},"hotels":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"address":{"nullable":true,"type":"string"},"city":{"nullable":true,"type":"string"},"region":{"nullable":true,"type":"string"},"country":{"nullable":true,"type":"string"},"postal_code":{"nullable":true,"type":"string"},"latitude":{"nullable":true,"type":"number"},"longitude":{"nullable":true,"type":"number"},"distance_km":{"nullable":true,"type":"number"},"rating":{"nullable":true,"type":"number"},"rating_count":{"nullable":true,"type":"number"}},"required":["code"],"additionalProperties":false}},"total":{"description":"Hotels in the radius, before paging.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"next_cursor":{"description":"Pass as ?cursor= for the next page; null on the last page or when unpaged.","nullable":true,"type":"string"},"shopped_at":{"type":"string"},"source":{"default":"live","type":"string","enum":["live","cached"]},"cache_age_sec":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["chain","supported","hotels","shopped_at","source","cache_age_sec"],"additionalProperties":false},"HotelDetailResponse":{"type":"object","properties":{"found":{"type":"boolean"},"hotel":{"nullable":true,"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"address":{"nullable":true,"type":"string"},"city":{"nullable":true,"type":"string"},"region":{"nullable":true,"type":"string"},"country":{"nullable":true,"type":"string"},"postal_code":{"nullable":true,"type":"string"},"latitude":{"nullable":true,"type":"number"},"longitude":{"nullable":true,"type":"number"},"star_rating":{"nullable":true,"type":"number"},"guest_rating":{"nullable":true,"type":"number"},"guest_rating_count":{"nullable":true,"type":"number"},"amenities":{"nullable":true,"type":"array","items":{"type":"string"}},"photos":{"nullable":true,"type":"array","items":{"type":"string"}},"description":{"nullable":true,"type":"string"},"updated_at":{"nullable":true,"type":"string"}},"required":["chain","code"],"additionalProperties":false},"source":{"default":"cached","type":"string","enum":["live","cached"]}},"required":["found","hotel","source"],"additionalProperties":false},"MatrixRequest":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_codes":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$"}},"nights":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."}},"required":["from","to"],"description":"Inclusive range of CHECKIN dates. Each date is one night-cell per hotel and los."},"los":{"default":[1],"minItems":1,"maxItems":3,"type":"array","items":{"type":"integer","minimum":1,"maximum":7}},"kind":{"default":"cash","description":"cash: lowest public/member rate per cell. award: lowest points per cell — refused on chains with no off-device points (see /chains award).","type":"string","enum":["cash","award"]},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"max_age":{"description":"Max acceptable cache age in seconds. Omitted: any cached answer within the cache TTL, at the cached rate. 0: always shop live, at the live rate. Not part of the cache key.","type":"integer","minimum":0,"maximum":9007199254740991},"webhook_url":{"description":"POSTed once when the job finishes (large matrices run asynchronously).","type":"string","maxLength":2000,"format":"uri"}},"required":["chain","hotel_codes","nights"]},"MatrixResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"kind":{"type":"string","enum":["cash","award"]},"los":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"currency":{"type":"string"},"cells_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells_served":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Cells answered now from the store, within max_age (default: the rate cache TTL). Billed at the cache-hit rate."},"cells":{"type":"array","items":{"type":"object","properties":{"hotel_code":{"type":"string"},"night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"],"description":"kind award: available when ANY award is sold; a cell can be available with lowest_points null when only a points+cash award exists (see lowest_points_cash)."},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"lowest":{"default":{},"description":"Same keys as ShopResponse.lowest: `bar`, `bar_after_tax`, `member`. Present on both kinds.","type":"object","additionalProperties":{"type":"number"}},"lowest_points":{"description":"Lowest COPAY-FREE award per night — the same figure whether the cell came from a shop or from the chain's calendar.","nullable":true,"type":"number"},"lowest_points_cash":{"description":"Points per night of the cheapest points+cash award (shop-sourced cells only; calendars do not carry it).","nullable":true,"type":"number"},"cash_copay":{"description":"Co-pay per night of `lowest_points_cash`.","nullable":true,"type":"number"},"cpp":{"description":"Cents per point of the copay-free award, over the night's lowest purchasable cash leg.","nullable":true,"type":"number"},"cpp_points_cash":{"nullable":true,"type":"number"},"currency":{"nullable":true,"type":"string"},"observed_at":{"type":"string","description":"When this cell was last refreshed from the chain."},"cache_age_sec":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"from_default_occupancy":{"description":"kind award only: this cell was served from the default-occupancy (2 adults, 1 room) cell because the exact occupancy had none; points rarely vary by occupancy.","type":"boolean"},"certs":{"description":"kind award only, present when non-empty: the free-night certificates that would cover this night's copay-free award, cheapest first, each with the top-up it needs. Marriott 35k/40k/50k/85k (top-up ≤ 15k), Hilton free night (any standard night), IHG 40k (top-up unlimited). Hyatt certificates are by category, which cells do not carry, so none is flagged. Judged at read time from program terms as of 2026-10-09.","type":"array","items":{"type":"object","properties":{"cert":{"type":"string","enum":["marriott_35k","marriott_40k","marriott_50k","marriott_85k","hilton_free_night","ihg_40k"]},"top_up":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Points the member adds on top of the certificate for this night; 0 = the certificate alone."}},"required":["cert","top_up"],"additionalProperties":false}}},"required":["hotel_code","night","los","status","lowest","observed_at","cache_age_sec"],"additionalProperties":false}},"job":{"nullable":true,"description":"The job filling the cells not served now; null when every cell was served. Poll GET /jobs/{id}, then GET /jobs/{id}/cells.","type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["matrix","ids"],"description":"matrix: cells (GET /jobs/{id}/cells, /export). ids: an id-map resolution job (POST /hotels/ids/jobs) — read the results from GET /hotels/{chain}/{code}/ids."},"status":{"type":"string","enum":["queued","running","done","failed","cancelled"]},"cells_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tasks_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Upstream calls the planner split the job into — fewer than cells where a chain has a native batch endpoint."},"tasks_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tasks_failed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"credits_reserved":{"type":"number","description":"Worst case quoted at creation (every cell shopped live). Checked against the key's quota before the job is accepted."},"credits_spent":{"type":"number","description":"What the tasks actually cost so far: cells served from cache bill the cache-hit rate."},"created_at":{"type":"string"},"finished_at":{"nullable":true,"type":"string"},"webhook_url":{"nullable":true,"type":"string"}},"required":["id","kind","status","cells_total","cells_done","tasks_total","tasks_done","tasks_failed","credits_reserved","credits_spent","created_at","finished_at","webhook_url"],"additionalProperties":false},"shopped_at":{"type":"string"}},"required":["chain","kind","los","occupancy","currency","cells_total","cells_served","cells","job","shopped_at"],"additionalProperties":false},"JobResponse":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["matrix","ids"],"description":"matrix: cells (GET /jobs/{id}/cells, /export). ids: an id-map resolution job (POST /hotels/ids/jobs) — read the results from GET /hotels/{chain}/{code}/ids."},"status":{"type":"string","enum":["queued","running","done","failed","cancelled"]},"cells_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tasks_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Upstream calls the planner split the job into — fewer than cells where a chain has a native batch endpoint."},"tasks_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tasks_failed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"credits_reserved":{"type":"number","description":"Worst case quoted at creation (every cell shopped live). Checked against the key's quota before the job is accepted."},"credits_spent":{"type":"number","description":"What the tasks actually cost so far: cells served from cache bill the cache-hit rate."},"created_at":{"type":"string"},"finished_at":{"nullable":true,"type":"string"},"webhook_url":{"nullable":true,"type":"string"}},"required":["id","kind","status","cells_total","cells_done","tasks_total","tasks_done","tasks_failed","credits_reserved","credits_spent","created_at","finished_at","webhook_url"],"additionalProperties":false},"JobCellsResponse":{"type":"object","properties":{"job_id":{"type":"string"},"cells":{"type":"array","items":{"type":"object","properties":{"hotel_code":{"type":"string"},"night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"],"description":"kind award: available when ANY award is sold; a cell can be available with lowest_points null when only a points+cash award exists (see lowest_points_cash)."},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"lowest":{"default":{},"description":"Same keys as ShopResponse.lowest: `bar`, `bar_after_tax`, `member`. Present on both kinds.","type":"object","additionalProperties":{"type":"number"}},"lowest_points":{"description":"Lowest COPAY-FREE award per night — the same figure whether the cell came from a shop or from the chain's calendar.","nullable":true,"type":"number"},"lowest_points_cash":{"description":"Points per night of the cheapest points+cash award (shop-sourced cells only; calendars do not carry it).","nullable":true,"type":"number"},"cash_copay":{"description":"Co-pay per night of `lowest_points_cash`.","nullable":true,"type":"number"},"cpp":{"description":"Cents per point of the copay-free award, over the night's lowest purchasable cash leg.","nullable":true,"type":"number"},"cpp_points_cash":{"nullable":true,"type":"number"},"currency":{"nullable":true,"type":"string"},"observed_at":{"type":"string","description":"When this cell was last refreshed from the chain."},"cache_age_sec":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"from_default_occupancy":{"description":"kind award only: this cell was served from the default-occupancy (2 adults, 1 room) cell because the exact occupancy had none; points rarely vary by occupancy.","type":"boolean"},"certs":{"description":"kind award only, present when non-empty: the free-night certificates that would cover this night's copay-free award, cheapest first, each with the top-up it needs. Marriott 35k/40k/50k/85k (top-up ≤ 15k), Hilton free night (any standard night), IHG 40k (top-up unlimited). Hyatt certificates are by category, which cells do not carry, so none is flagged. Judged at read time from program terms as of 2026-10-09.","type":"array","items":{"type":"object","properties":{"cert":{"type":"string","enum":["marriott_35k","marriott_40k","marriott_50k","marriott_85k","hilton_free_night","ihg_40k"]},"top_up":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Points the member adds on top of the certificate for this night; 0 = the certificate alone."}},"required":["cert","top_up"],"additionalProperties":false}}},"required":["hotel_code","night","los","status","lowest","observed_at","cache_age_sec"],"additionalProperties":false}},"next_cursor":{"nullable":true,"description":"Pass as ?cursor= for the next page; null on the last page.","type":"string"}},"required":["job_id","cells","next_cursor"],"additionalProperties":false},"WatchRequest":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_code":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$"},"nights":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."}},"required":["from","to"],"description":"Inclusive range of CHECKIN dates; at most 90. A rebook watch covers one stay: from == to."},"los":{"default":1,"type":"integer","minimum":1,"maximum":7},"kind":{"default":"award","type":"string","enum":["cash","award"]},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"condition":{"default":"any","description":"available: fire when a night becomes available. points_lte: fire when lowest_points drops to <= threshold (kind award). any: fire on every change of the compared fields. rebook: fire when the direct rate (kind cash) or the award (kind award) drops under what the guest paid by at least rebook.min_saving_pct — the rebooking alert. undercut: fire when an OTA channel's public rate drops under the direct public rate by at least undercut.min_pct — the parity alert (kind cash).","type":"string","enum":["available","points_lte","any","rebook","undercut"]},"threshold":{"description":"Required for condition points_lte.","type":"number","minimum":0,"exclusiveMinimum":true},"rebook":{"description":"Required for condition rebook.","type":"object","properties":{"paid_rate":{"description":"What the guest pays PER NIGHT, in the watch's currency (kind cash). Give this or paid_total.","type":"number","minimum":0,"exclusiveMinimum":true},"paid_total":{"description":"What the guest pays for the whole stay; divided by los.","type":"number","minimum":0,"exclusiveMinimum":true},"paid_includes_tax":{"default":false,"description":"true: compare against the night's after-tax figure (lowest.bar_after_tax); false: against the native-basis figure (lowest.bar). Match what the booking shows.","type":"boolean"},"paid_points":{"description":"Points PER NIGHT the guest redeemed (kind award). The watch fires when lowest_points drops under it by min_saving_pct.","type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":9007199254740991},"min_saving_pct":{"default":5,"description":"Fire only when the saving is at least this percent of what was paid. 0 = any drop.","type":"number","minimum":0,"maximum":90},"compare":{"default":"bar","description":"Which cash figure to compare: the public rate, or the member rate when the guest can book as a member.","type":"string","enum":["bar","member"]}}},"undercut":{"description":"Required for condition undercut.","type":"object","properties":{"channel":{"type":"string","enum":["booking","expedia","hotelscom","ctrip","agoda"],"description":"The OTA to compare against."},"hotel_code":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$","description":"The hotel's id on that channel (GET /hotels/{chain}/{code}/ids)."},"min_pct":{"default":2,"description":"Fire when the channel's public rate is under the direct public rate by at least this percent. 0 = any undercut.","type":"number","minimum":0,"maximum":90},"compare":{"default":"after_tax","description":"after_tax: compare lowest.bar_after_tax on both sides (the comparable basis; a chain without tax data never fires). native: each side's own lowest.bar.","type":"string","enum":["after_tax","native"]}},"required":["channel","hotel_code"]},"webhook_url":{"type":"string","maxLength":2000,"format":"uri"},"refresh_sec":{"default":21600,"description":"How often the watched nights are refreshed. This is what a watch costs: credits_per_refresh × refreshes.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["chain","hotel_code","nights","webhook_url"]},"WatchResponse":{"type":"object","properties":{"id":{"type":"string"},"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_code":{"type":"string"},"nights":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"required":["from","to"],"additionalProperties":false},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"kind":{"type":"string","enum":["cash","award"]},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"currency":{"type":"string"},"condition":{"type":"string","enum":["available","points_lte","any","rebook","undercut"]},"threshold":{"nullable":true,"type":"number"},"rebook":{"nullable":true,"type":"object","properties":{"paid_rate":{"description":"What the guest pays PER NIGHT, in the watch's currency (kind cash). Give this or paid_total.","type":"number","minimum":0,"exclusiveMinimum":true},"paid_total":{"description":"What the guest pays for the whole stay; divided by los.","type":"number","minimum":0,"exclusiveMinimum":true},"paid_includes_tax":{"default":false,"description":"true: compare against the night's after-tax figure (lowest.bar_after_tax); false: against the native-basis figure (lowest.bar). Match what the booking shows.","type":"boolean"},"paid_points":{"description":"Points PER NIGHT the guest redeemed (kind award). The watch fires when lowest_points drops under it by min_saving_pct.","type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":9007199254740991},"min_saving_pct":{"default":5,"description":"Fire only when the saving is at least this percent of what was paid. 0 = any drop.","type":"number","minimum":0,"maximum":90},"compare":{"default":"bar","description":"Which cash figure to compare: the public rate, or the member rate when the guest can book as a member.","type":"string","enum":["bar","member"]}},"required":["paid_includes_tax","min_saving_pct","compare"],"additionalProperties":false},"undercut":{"nullable":true,"type":"object","properties":{"channel":{"type":"string","enum":["booking","expedia","hotelscom","ctrip","agoda"],"description":"The OTA to compare against."},"hotel_code":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$","description":"The hotel's id on that channel (GET /hotels/{chain}/{code}/ids)."},"min_pct":{"default":2,"description":"Fire when the channel's public rate is under the direct public rate by at least this percent. 0 = any undercut.","type":"number","minimum":0,"maximum":90},"compare":{"default":"after_tax","description":"after_tax: compare lowest.bar_after_tax on both sides (the comparable basis; a chain without tax data never fires). native: each side's own lowest.bar.","type":"string","enum":["after_tax","native"]}},"required":["channel","hotel_code","min_pct","compare"],"additionalProperties":false},"webhook_url":{"type":"string"},"refresh_sec":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"credits_per_refresh":{"type":"number","description":"What one refresh of this watch's nights bills: cells × the chain's per-cell price × the cache-hit multiplier — a refresh is shared with every other watch on the same nights."},"status":{"type":"string","enum":["active","paused","expired"],"description":"active: refreshed on schedule. paused: by the customer; resume to continue. expired: night_to has passed (earlier than yesterday UTC) — set by the sweep, cannot be resumed; create a new watch."},"next_refresh_at":{"type":"string"},"last_refreshed_at":{"nullable":true,"type":"string"},"created_at":{"type":"string"},"secret":{"description":"HMAC-SHA256 key for X-Directrate-Signature. Returned ONCE, on creation; store it.","type":"string"}},"required":["id","chain","hotel_code","nights","los","kind","occupancy","currency","condition","threshold","webhook_url","refresh_sec","cells","credits_per_refresh","status","next_refresh_at","last_refreshed_at","created_at"],"additionalProperties":false},"EventsResponse":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object","properties":{"event_id":{"type":"string"},"watch_id":{"type":"string"},"type":{"type":"string","enum":["initial","changed","met"],"description":"initial: the first observation of a night after the watch was created. changed: a compared field moved (condition any). met: the condition became true (available / points_lte)."},"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_code":{"type":"string"},"night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"kind":{"type":"string","enum":["cash","award"]},"prev":{"nullable":true,"description":"The night's state before this event; null on initial.","type":"object","properties":{"hotel_code":{"type":"string"},"night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"],"description":"kind award: available when ANY award is sold; a cell can be available with lowest_points null when only a points+cash award exists (see lowest_points_cash)."},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"lowest":{"default":{},"description":"Same keys as ShopResponse.lowest: `bar`, `bar_after_tax`, `member`. Present on both kinds.","type":"object","additionalProperties":{"type":"number"}},"lowest_points":{"description":"Lowest COPAY-FREE award per night — the same figure whether the cell came from a shop or from the chain's calendar.","nullable":true,"type":"number"},"lowest_points_cash":{"description":"Points per night of the cheapest points+cash award (shop-sourced cells only; calendars do not carry it).","nullable":true,"type":"number"},"cash_copay":{"description":"Co-pay per night of `lowest_points_cash`.","nullable":true,"type":"number"},"cpp":{"description":"Cents per point of the copay-free award, over the night's lowest purchasable cash leg.","nullable":true,"type":"number"},"cpp_points_cash":{"nullable":true,"type":"number"},"currency":{"nullable":true,"type":"string"},"observed_at":{"type":"string","description":"When this cell was last refreshed from the chain."},"cache_age_sec":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"from_default_occupancy":{"description":"kind award only: this cell was served from the default-occupancy (2 adults, 1 room) cell because the exact occupancy had none; points rarely vary by occupancy.","type":"boolean"},"certs":{"description":"kind award only, present when non-empty: the free-night certificates that would cover this night's copay-free award, cheapest first, each with the top-up it needs. Marriott 35k/40k/50k/85k (top-up ≤ 15k), Hilton free night (any standard night), IHG 40k (top-up unlimited). Hyatt certificates are by category, which cells do not carry, so none is flagged. Judged at read time from program terms as of 2026-10-09.","type":"array","items":{"type":"object","properties":{"cert":{"type":"string","enum":["marriott_35k","marriott_40k","marriott_50k","marriott_85k","hilton_free_night","ihg_40k"]},"top_up":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Points the member adds on top of the certificate for this night; 0 = the certificate alone."}},"required":["cert","top_up"],"additionalProperties":false}}},"required":["hotel_code","night","los","status","lowest","observed_at","cache_age_sec"],"additionalProperties":false},"next":{"type":"object","properties":{"hotel_code":{"type":"string"},"night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"],"description":"kind award: available when ANY award is sold; a cell can be available with lowest_points null when only a points+cash award exists (see lowest_points_cash)."},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"lowest":{"default":{},"description":"Same keys as ShopResponse.lowest: `bar`, `bar_after_tax`, `member`. Present on both kinds.","type":"object","additionalProperties":{"type":"number"}},"lowest_points":{"description":"Lowest COPAY-FREE award per night — the same figure whether the cell came from a shop or from the chain's calendar.","nullable":true,"type":"number"},"lowest_points_cash":{"description":"Points per night of the cheapest points+cash award (shop-sourced cells only; calendars do not carry it).","nullable":true,"type":"number"},"cash_copay":{"description":"Co-pay per night of `lowest_points_cash`.","nullable":true,"type":"number"},"cpp":{"description":"Cents per point of the copay-free award, over the night's lowest purchasable cash leg.","nullable":true,"type":"number"},"cpp_points_cash":{"nullable":true,"type":"number"},"currency":{"nullable":true,"type":"string"},"observed_at":{"type":"string","description":"When this cell was last refreshed from the chain."},"cache_age_sec":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"from_default_occupancy":{"description":"kind award only: this cell was served from the default-occupancy (2 adults, 1 room) cell because the exact occupancy had none; points rarely vary by occupancy.","type":"boolean"},"certs":{"description":"kind award only, present when non-empty: the free-night certificates that would cover this night's copay-free award, cheapest first, each with the top-up it needs. Marriott 35k/40k/50k/85k (top-up ≤ 15k), Hilton free night (any standard night), IHG 40k (top-up unlimited). Hyatt certificates are by category, which cells do not carry, so none is flagged. Judged at read time from program terms as of 2026-10-09.","type":"array","items":{"type":"object","properties":{"cert":{"type":"string","enum":["marriott_35k","marriott_40k","marriott_50k","marriott_85k","hilton_free_night","ihg_40k"]},"top_up":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Points the member adds on top of the certificate for this night; 0 = the certificate alone."}},"required":["cert","top_up"],"additionalProperties":false}}},"required":["hotel_code","night","los","status","lowest","observed_at","cache_age_sec"],"additionalProperties":false},"rebook":{"description":"condition rebook only: the comparison that fired.","nullable":true,"type":"object","properties":{"basis":{"type":"string","description":"Which figure was compared: bar, bar_after_tax, member, or points."},"paid":{"type":"number","description":"Per night, as the watch stated it."},"current":{"type":"number","description":"The night's figure now."},"saving":{"type":"number","description":"paid − current, per night."},"saving_pct":{"type":"number"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"saving_total":{"type":"number","description":"saving × los."}},"required":["basis","paid","current","saving","saving_pct","los","saving_total"],"additionalProperties":false},"undercut":{"description":"condition undercut only: the comparison that fired.","nullable":true,"type":"object","properties":{"channel":{"type":"string","enum":["booking","expedia","hotelscom","ctrip","agoda"]},"channel_hotel_code":{"type":"string"},"basis":{"type":"string","enum":["bar_after_tax","bar"]},"direct":{"type":"number","description":"The direct public rate per night, on `basis`."},"channel_rate":{"type":"number","description":"The channel's public rate per night, on `basis`."},"diff":{"type":"number","description":"channel_rate − direct (negative: the channel undercuts)."},"diff_pct":{"type":"number","description":"As a percentage of direct."},"channel_observed_at":{"type":"string"}},"required":["channel","channel_hotel_code","basis","direct","channel_rate","diff","diff_pct","channel_observed_at"],"additionalProperties":false},"created_at":{"type":"string"},"delivered_at":{"nullable":true,"type":"string"},"attempts":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["event_id","watch_id","type","chain","hotel_code","night","los","kind","prev","next","created_at","delivered_at","attempts"],"additionalProperties":false}},"next_cursor":{"nullable":true,"type":"string"}},"required":["events","next_cursor"],"additionalProperties":false},"AreaRequest":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"q":{"type":"string","maxLength":200},"place_id":{"type":"string","maxLength":300},"lat":{"type":"number","minimum":-90,"maximum":90},"lng":{"type":"number","minimum":-180,"maximum":180},"radius":{"default":30,"type":"number","minimum":1,"maximum":100},"country":{"type":"string","pattern":"^[A-Za-z]{2,3}$"},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"los":{"default":1,"type":"integer","minimum":1,"maximum":7},"kind":{"default":"award","type":"string","enum":["cash","award"]},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"max_age":{"description":"Max acceptable cache age in seconds. Omitted: any cached answer within the cache TTL, at the cached rate. 0: always shop live, at the live rate. Not part of the cache key.","type":"integer","minimum":0,"maximum":9007199254740991},"max_hotels":{"default":200,"description":"Nearest N hotels to search (by distance). The matrix cap is 200 per request.","type":"integer","minimum":1,"maximum":200},"webhook_url":{"type":"string","maxLength":2000,"format":"uri"}},"required":["chain","checkin"]},"AwardSearchRequest":{"type":"object","properties":{"chains":{"description":"Chains to search. Omitted: every chain with off-device award points AND a geo-search (see /chains award, hotels). Chains lacking either are skipped and named in `skipped`.","minItems":1,"maxItems":11,"type":"array","items":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]}},"q":{"type":"string","maxLength":200},"place_id":{"type":"string","maxLength":300},"lat":{"type":"number","minimum":-90,"maximum":90},"lng":{"type":"number","minimum":-180,"maximum":180},"radius":{"default":30,"description":"Miles, as GET /hotels.","type":"number","minimum":1,"maximum":100},"country":{"type":"string","pattern":"^[A-Za-z]{2,3}$"},"nights":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."}},"required":["from","to"],"description":"Inclusive range of CHECKIN nights, at most 62. Each night is judged on its own (los 1): the flexible-dates search."},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"description":"Points rarely vary by occupancy; a night missing for this occupancy is served from the default-occupancy cell (flagged), as /matrix award does.","type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"max_points":{"description":"Only nights whose copay-free award costs at most this many points.","type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":9007199254740991},"min_cpp":{"description":"Only nights whose cpp (cents per point, copay-free award) is at least this.","type":"number","minimum":0,"exclusiveMinimum":true},"cert":{"description":"Only nights a free-night certificate of this kind would cover (see MatrixCell.certs). Chains the certificate does not belong to list nothing.","type":"string","enum":["marriott_35k","marriott_40k","marriott_50k","marriott_85k","hilton_free_night","ihg_40k"]},"max_top_up":{"description":"With `cert`: only nights whose top-up is at most this many points (0 = the certificate alone must cover the night).","type":"integer","minimum":0,"maximum":9007199254740991},"rank":{"default":"points","description":"points: hotels by their cheapest matching night; cpp: by their best cpp; nights: by how many nights match.","type":"string","enum":["points","cpp","nights"]},"max_hotels":{"default":50,"description":"Nearest N hotels PER CHAIN to consider.","type":"integer","minimum":1,"maximum":100},"limit":{"default":25,"description":"Hotels returned (after ranking). Only returned hotels are billed.","type":"integer","minimum":1,"maximum":100},"max_age":{"description":"Max acceptable cell age in seconds. Default 21600 (6 h) — see reports/award-search-design.md. Cells older than this are not served; whether they are refreshed depends on `fill`.","type":"integer","minimum":0,"maximum":604800},"fill":{"default":"missing","description":"What to refresh live, as a /matrix job per chain, quoted in credits.fill_reserved: missing = hotels with NO cell in the window; stale = also hotels whose cells are older than max_age; none = answer from the store only.","type":"string","enum":["missing","stale","none"]},"webhook_url":{"description":"POSTed once per fill job when it finishes.","type":"string","maxLength":2000,"format":"uri"}},"required":["nights"]},"AwardSearchResponse":{"type":"object","properties":{"chains":{"type":"array","items":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"description":"Chains searched."},"skipped":{"type":"array","items":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"reason":{"type":"string"}},"required":["chain","reason"],"additionalProperties":false},"description":"Chains asked for but not searched, and why."},"center":{"nullable":true,"type":"object","properties":{"label":{"nullable":true,"type":"string"},"latitude":{"nullable":true,"type":"number"},"longitude":{"nullable":true,"type":"number"}},"additionalProperties":false},"nights":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"required":["from","to"],"additionalProperties":false},"los":{"type":"number","enum":[1]},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"currency":{"type":"string"},"hotels_found":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Hotels the chains returned in the radius, summed."},"hotels_searched":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Of those, the nearest max_hotels per chain whose cells were read."},"hotels_returned":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hotels":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"address":{"nullable":true,"type":"string"},"city":{"nullable":true,"type":"string"},"region":{"nullable":true,"type":"string"},"country":{"nullable":true,"type":"string"},"postal_code":{"nullable":true,"type":"string"},"latitude":{"nullable":true,"type":"number"},"longitude":{"nullable":true,"type":"number"},"distance_km":{"nullable":true,"type":"number"},"rating":{"nullable":true,"type":"number"},"rating_count":{"nullable":true,"type":"number"},"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"nights_known":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Nights in the window the store had a cell for, within max_age."},"nights_available":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Of those, nights with a copay-free award on sale that pass the filters — the ones listed in `nights`."},"nights_sold_out":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Known nights with no copay-free award on sale (sold out, closed, or the hotel sells no award that night)."},"nights_filtered":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Known nights with an award that failed max_points or min_cpp."},"min_points":{"nullable":true,"description":"Cheapest listed night.","type":"number"},"min_points_night":{"nullable":true,"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"best_cpp":{"nullable":true,"type":"number"},"best_cpp_night":{"nullable":true,"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"nights":{"type":"array","items":{"type":"object","properties":{"night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"lowest_points":{"type":"number","description":"The copay-free award for this night (what the chain's calendar reports)."},"cpp":{"nullable":true,"type":"number"},"lowest_points_cash":{"nullable":true,"type":"number"},"cash_copay":{"nullable":true,"type":"number"},"cpp_points_cash":{"nullable":true,"type":"number"},"lowest":{"default":{},"description":"Cash for the same night where the cell carries it: bar, bar_after_tax, member.","type":"object","additionalProperties":{"type":"number"}},"currency":{"nullable":true,"type":"string"},"observed_at":{"type":"string"},"cache_age_sec":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"from_default_occupancy":{"type":"boolean"},"certs":{"description":"As MatrixCell.certs.","type":"array","items":{"type":"object","properties":{"cert":{"type":"string","enum":["marriott_35k","marriott_40k","marriott_50k","marriott_85k","hilton_free_night","ihg_40k"]},"top_up":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Points the member adds on top of the certificate for this night; 0 = the certificate alone."}},"required":["cert","top_up"],"additionalProperties":false}}},"required":["night","lowest_points","lowest","observed_at","cache_age_sec"],"additionalProperties":false},"description":"Only the available, matching nights — these are what the search bills."},"credits":{"type":"number","description":"What this hotel's listed nights cost in this answer, after the per-hotel cap."}},"required":["code","chain","nights_known","nights_available","nights_sold_out","nights_filtered","min_points","min_points_night","best_cpp","best_cpp_night","nights","credits"],"additionalProperties":false},"description":"Ranked per `rank`; hotels with no matching night come after, by distance, with their counts."},"freshness":{"type":"object","properties":{"max_age":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells_known":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Cells the store held for the searched hotels × nights, regardless of age."},"cells_served":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Of those, within max_age — the ones this answer is built from."},"cells_missing":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Hotel-nights with no cell within max_age."},"oldest_served_at":{"nullable":true,"type":"string"}},"required":["max_age","cells_known","cells_served","cells_missing","oldest_served_at"],"additionalProperties":false},"jobs":{"type":"array","items":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotels":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"job":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["matrix","ids"],"description":"matrix: cells (GET /jobs/{id}/cells, /export). ids: an id-map resolution job (POST /hotels/ids/jobs) — read the results from GET /hotels/{chain}/{code}/ids."},"status":{"type":"string","enum":["queued","running","done","failed","cancelled"]},"cells_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tasks_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Upstream calls the planner split the job into — fewer than cells where a chain has a native batch endpoint."},"tasks_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tasks_failed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"credits_reserved":{"type":"number","description":"Worst case quoted at creation (every cell shopped live). Checked against the key's quota before the job is accepted."},"credits_spent":{"type":"number","description":"What the tasks actually cost so far: cells served from cache bill the cache-hit rate."},"created_at":{"type":"string"},"finished_at":{"nullable":true,"type":"string"},"webhook_url":{"nullable":true,"type":"string"}},"required":["id","kind","status","cells_total","cells_done","tasks_total","tasks_done","tasks_failed","credits_reserved","credits_spent","created_at","finished_at","webhook_url"],"additionalProperties":false}},"required":["chain","hotels","job"],"additionalProperties":false},"description":"One /matrix fill job per chain, per `fill`. Poll GET /jobs/{id}; re-run the search when done — the store then answers."},"credits":{"type":"object","properties":{"lookups":{"type":"number","description":"The per-chain place lookups (GET /hotels rates; cached 24 h)."},"search":{"type":"number","description":"The listed nights of the returned hotels at the cache-hit rate, capped per hotel, with a one-credit floor — PRICING.md §9."},"fill_reserved":{"type":"number","description":"Worst case of the fill jobs, reserved now; the jobs charge what they actually did."}},"required":["lookups","search","fill_reserved"],"additionalProperties":false},"shopped_at":{"type":"string"}},"required":["chains","skipped","center","nights","los","occupancy","currency","hotels_found","hotels_searched","hotels_returned","hotels","freshness","jobs","credits","shopped_at"],"additionalProperties":false},"AwardValueResponse":{"type":"object","properties":{"nights":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"required":["from","to"],"additionalProperties":false},"chains":{"type":"array","items":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"description":"Chains the table was read over."},"country":{"nullable":true,"type":"string"},"city":{"nullable":true,"type":"string"},"currency":{"type":"string"},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false,"description":"Always the default occupancy: points rarely vary by it, and that is the cell every chain's calendar fills."},"rank":{"type":"string","enum":["best","avg"]},"hotels_matching":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Hotels with at least min_nights qualifying nights, before `limit`."},"hotels":{"type":"array","items":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"city":{"nullable":true,"type":"string"},"region":{"nullable":true,"type":"string"},"country":{"nullable":true,"type":"string"},"latitude":{"nullable":true,"type":"number"},"longitude":{"nullable":true,"type":"number"},"nights_known":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Nights in the window the store had a fresh cell for."},"nights_available":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Of those, nights with a copay-free award and a cpp passing min_cpp."},"best_cpp":{"type":"number"},"best_cpp_night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"avg_cpp":{"type":"number","description":"Mean cpp over the qualifying nights."},"min_points":{"type":"number"},"min_points_night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"best":{"type":"object","properties":{"night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"lowest_points":{"type":"number","description":"The copay-free award for this night (what the chain's calendar reports)."},"cpp":{"nullable":true,"type":"number"},"lowest_points_cash":{"nullable":true,"type":"number"},"cash_copay":{"nullable":true,"type":"number"},"cpp_points_cash":{"nullable":true,"type":"number"},"lowest":{"default":{},"description":"Cash for the same night where the cell carries it: bar, bar_after_tax, member.","type":"object","additionalProperties":{"type":"number"}},"currency":{"nullable":true,"type":"string"},"observed_at":{"type":"string"},"cache_age_sec":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"from_default_occupancy":{"type":"boolean"},"certs":{"description":"As MatrixCell.certs.","type":"array","items":{"type":"object","properties":{"cert":{"type":"string","enum":["marriott_35k","marriott_40k","marriott_50k","marriott_85k","hilton_free_night","ihg_40k"]},"top_up":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Points the member adds on top of the certificate for this night; 0 = the certificate alone."}},"required":["cert","top_up"],"additionalProperties":false}}},"required":["night","lowest_points","lowest","observed_at","cache_age_sec"],"additionalProperties":false,"description":"The best-cpp night in full: points, cash legs, certs, when it was observed."},"credits":{"type":"number"}},"required":["chain","code","nights_known","nights_available","best_cpp","best_cpp_night","avg_cpp","min_points","min_points_night","best","credits"],"additionalProperties":false}},"freshness":{"type":"object","properties":{"max_age":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"oldest_served_at":{"nullable":true,"type":"string"}},"required":["max_age","oldest_served_at"],"additionalProperties":false},"credits":{"type":"number","description":"One award night-unit per hotel returned (the chain's cache-hit award night, 0.025 on calendar chains), floor 1 — PRICING.md §11."},"shopped_at":{"type":"string"}},"required":["nights","chains","country","city","currency","occupancy","rank","hotels_matching","hotels","freshness","credits","shopped_at"],"additionalProperties":false},"CalendarAreaRequest":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"q":{"type":"string","maxLength":200},"place_id":{"type":"string","maxLength":300},"lat":{"type":"number","minimum":-90,"maximum":90},"lng":{"type":"number","minimum":-180,"maximum":180},"radius":{"default":30,"description":"Miles, as GET /hotels.","type":"number","minimum":1,"maximum":100},"country":{"type":"string","pattern":"^[A-Za-z]{2,3}$"},"nights":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."}},"required":["from","to"],"description":"Inclusive range of CHECKIN nights, at most 62; every night is judged on its own (los 1)."},"kind":{"default":"award","description":"award: copay-free points per night (refused on chains with no off-device points). cash: the public bar per night.","type":"string","enum":["cash","award"]},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"max_age":{"description":"Max acceptable cell age in seconds. Default 21600 (6 h) for award, 3600 (1 h) for cash. Older cells are not served (the /matrix rule); `fill` decides whether they are refreshed.","type":"integer","minimum":0,"maximum":604800},"max_hotels":{"default":50,"description":"Nearest N hotels to read (and to fill).","type":"integer","minimum":1,"maximum":200},"fill":{"default":"missing","description":"As /award/search: missing = one /matrix job for hotels with no cell in the window; stale = also hotels whose cells are older than max_age; none = store only.","type":"string","enum":["missing","stale","none"]},"webhook_url":{"type":"string","maxLength":2000,"format":"uri"}},"required":["chain","nights"]},"ExportRequest":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_codes":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$"}},"nights":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."}},"required":["from","to"],"description":"Inclusive range of CHECKIN dates. Each date is one night-cell per hotel and los."},"los":{"default":[1],"minItems":1,"maxItems":3,"type":"array","items":{"type":"integer","minimum":1,"maximum":7}},"kind":{"default":"cash","description":"cash: lowest public/member rate per cell. award: lowest points per cell — refused on chains with no off-device points (see /chains award).","type":"string","enum":["cash","award"]},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"max_age":{"description":"Max acceptable cache age in seconds. Omitted: any cached answer within the cache TTL, at the cached rate. 0: always shop live, at the live rate. Not part of the cache key.","type":"integer","minimum":0,"maximum":9007199254740991},"webhook_url":{"description":"POSTed once when the job finishes (large matrices run asynchronously).","type":"string","maxLength":2000,"format":"uri"},"format":{"default":"csv","description":"csv: one header row then one row per cell (RFC 4180 quoting). jsonl: one MatrixCell JSON object per line.","type":"string","enum":["csv","jsonl"]}},"required":["chain","hotel_codes","nights"]},"StayRequest":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_code":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$"},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"los":{"type":"integer","minimum":2,"maximum":14,"description":"Nights of the stay, 2 to 14."},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"max_age":{"description":"Max acceptable cache age in seconds. Omitted: any cached answer within the cache TTL, at the cached rate. 0: always shop live, at the live rate. Not part of the cache key.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["chain","hotel_code","checkin","los"]},"StayResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"currency":{"nullable":true,"type":"string"}},"required":["code"],"additionalProperties":false},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"checkout":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"currency":{"type":"string"},"stay":{"type":"object","properties":{"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"]},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"lowest":{"default":{},"description":"The los-night shop's lowest NIGHTLY figures (averaged over the stay where the chain prices nights differently) — as /shop.","type":"object","additionalProperties":{"type":"number"}},"lowest_points":{"nullable":true,"description":"The stay's copay-free award per night, when the chain returns points.","type":"number"},"rates":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Rate plans the shop returned."},"source":{"type":"string","enum":["live","cached"]},"cache_age_sec":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["status","lowest","lowest_points","rates","source","cache_age_sec"],"additionalProperties":false},"nights":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"]},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"lowest":{"default":{},"description":"Lowest nightly figures for this single night — same keys as ShopResponse.lowest (`bar`, `member`, `bar_after_tax`, `member_after_tax`).","type":"object","additionalProperties":{"type":"number"}},"lowest_points":{"description":"Lowest COPAY-FREE award for this night (the standard free night). Points+cash awards never lower it.","nullable":true,"type":"number"},"currency":{"nullable":true,"type":"string"},"source":{"type":"string","enum":["live","cached"]},"cache_age_sec":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["date","status","lowest","source","cache_age_sec"],"additionalProperties":false},"description":"Each night of the stay priced on its own (los 1) — the /calendar row for it, with min_los where the chain states one."},"totals":{"type":"object","properties":{"stay_bar":{"nullable":true,"description":"The stay's lowest public rate × los, in the chain's native tax basis (lowest.bar of the los-night shop).","type":"number"},"nightly_bar":{"nullable":true,"description":"The nights' lowest public rates added up — null when any night lacks one.","type":"number"},"stay_member":{"nullable":true,"type":"number"},"nightly_member":{"nullable":true,"type":"number"},"stay_bar_after_tax":{"nullable":true,"type":"number"},"nightly_bar_after_tax":{"nullable":true,"type":"number"}},"required":["stay_bar","nightly_bar","stay_member","nightly_member","stay_bar_after_tax","nightly_bar_after_tax"],"additionalProperties":false},"split":{"type":"object","properties":{"bookable":{"type":"boolean","description":"Whether every night can be booked on its own: available, and no minimum stay above 1 on it."},"blocked_nights":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"reason":{"type":"string"}},"required":["date","reason"],"additionalProperties":false}},"cheaper":{"nullable":true,"description":"Which is cheaper on the public rate, native basis; null when one side is unknown.","type":"string","enum":["stay","nightly","equal"]},"saving":{"nullable":true,"description":"Positive when booking night by night beats the stay rate (nightly_bar < stay_bar), negative when the stay rate wins; same basis as the totals.","type":"number"},"saving_pct":{"nullable":true,"description":"`saving` as a percentage of stay_bar.","type":"number"}},"required":["bookable","blocked_nights","cheaper","saving","saving_pct"],"additionalProperties":false},"credits":{"type":"object","properties":{"stay":{"type":"number","description":"The shop, as /shop bills it."},"nights":{"type":"number","description":"The nights, as /calendar bills them."}},"required":["stay","nights"],"additionalProperties":false},"shopped_at":{"type":"string"}},"required":["chain","hotel","checkin","checkout","los","occupancy","currency","stay","nights","totals","split","credits","shopped_at"],"additionalProperties":false},"ParityRequest":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel_code":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$"},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"los":{"default":1,"type":"integer","minimum":1,"maximum":14},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"channels":{"default":["booking","expedia","hotelscom","ctrip","agoda"],"description":"Which OTAs to shop beside the direct rate.","minItems":1,"maxItems":5,"type":"array","items":{"type":"string","enum":["booking","expedia","hotelscom","ctrip","agoda"]}},"ids":{"description":"The hotel's id on a channel, when you know it. Overrides the id map for this call; nothing is stored.","type":"object","properties":{"booking":{"type":"string"},"expedia":{"type":"string"},"hotelscom":{"type":"string"},"ctrip":{"type":"string"},"agoda":{"type":"string"}}},"min_confidence":{"default":0.6,"description":"Mapped ids under this confidence are not used (see GET /hotels/{chain}/{code}/ids).","type":"number","minimum":0,"maximum":1},"resolve":{"default":true,"description":"Look up missing ids live (the id-map resolution, billed as GET /hotels/{chain}/{code}/ids) before shopping.","type":"boolean"},"max_age":{"description":"Max acceptable cache age in seconds. Omitted: any cached answer within the cache TTL, at the cached rate. 0: always shop live, at the live rate. Not part of the cache key.","type":"integer","minimum":0,"maximum":9007199254740991}},"required":["chain","hotel_code","checkin"]},"ParityResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"currency":{"nullable":true,"type":"string"}},"required":["code"],"additionalProperties":false},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"currency":{"type":"string"},"direct":{"type":"object","properties":{"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"]},"public":{"nullable":true,"type":"object","properties":{"per_night":{"type":"number"},"total":{"type":"number"},"currency":{"nullable":true,"description":"The side's own currency. A channel in another currency than direct is shown but not compared (vs_direct null) — Marriott quotes a Bangkok hotel in THB while the OTAs answer in the requested USD.","type":"string"},"basis":{"type":"string","enum":["after_tax","native"],"description":"after_tax when the side carries an after-tax figure (the comparable one); native when it only has its own basis."},"rate_name":{"nullable":true,"type":"string"},"refundable":{"nullable":true,"type":"boolean"}},"required":["per_night","total","currency","basis"],"additionalProperties":false},"member":{"nullable":true,"type":"object","properties":{"per_night":{"type":"number"},"total":{"type":"number"},"currency":{"nullable":true,"description":"The side's own currency. A channel in another currency than direct is shown but not compared (vs_direct null) — Marriott quotes a Bangkok hotel in THB while the OTAs answer in the requested USD.","type":"string"},"basis":{"type":"string","enum":["after_tax","native"],"description":"after_tax when the side carries an after-tax figure (the comparable one); native when it only has its own basis."},"rate_name":{"nullable":true,"type":"string"},"refundable":{"nullable":true,"type":"boolean"}},"required":["per_night","total","currency","basis"],"additionalProperties":false},"points":{"nullable":true,"type":"object","properties":{"lowest_points":{"type":"number"},"cpp":{"nullable":true,"type":"number"}},"required":["lowest_points","cpp"],"additionalProperties":false},"rates":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"source":{"type":"string","enum":["live","cached"]},"cache_age_sec":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"credits":{"type":"number"}},"required":["status","public","member","points","rates","source","cache_age_sec","credits"],"additionalProperties":false},"channels":{"type":"array","items":{"type":"object","properties":{"channel":{"type":"string","enum":["booking","expedia","hotelscom","ctrip","agoda"]},"id":{"nullable":true,"description":"The hotel's id on the channel, or null when none is mapped.","type":"string"},"confidence":{"nullable":true,"type":"number"},"status":{"nullable":true,"description":"null when the channel was not shopped (no id, or an error — see `skipped`).","type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"]},"public":{"nullable":true,"description":"The channel's lowest rate anyone can book.","type":"object","properties":{"per_night":{"type":"number"},"total":{"type":"number"},"currency":{"nullable":true,"description":"The side's own currency. A channel in another currency than direct is shown but not compared (vs_direct null) — Marriott quotes a Bangkok hotel in THB while the OTAs answer in the requested USD.","type":"string"},"basis":{"type":"string","enum":["after_tax","native"],"description":"after_tax when the side carries an after-tax figure (the comparable one); native when it only has its own basis."},"rate_name":{"nullable":true,"type":"string"},"refundable":{"nullable":true,"type":"boolean"}},"required":["per_night","total","currency","basis"],"additionalProperties":false},"member":{"nullable":true,"description":"The channel's lowest rate that needs a (free) account — Genius, Member Price, Member deal.","type":"object","properties":{"per_night":{"type":"number"},"total":{"type":"number"},"currency":{"nullable":true,"description":"The side's own currency. A channel in another currency than direct is shown but not compared (vs_direct null) — Marriott quotes a Bangkok hotel in THB while the OTAs answer in the requested USD.","type":"string"},"basis":{"type":"string","enum":["after_tax","native"],"description":"after_tax when the side carries an after-tax figure (the comparable one); native when it only has its own basis."},"rate_name":{"nullable":true,"type":"string"},"refundable":{"nullable":true,"type":"boolean"}},"required":["per_night","total","currency","basis"],"additionalProperties":false},"vs_direct":{"nullable":true,"type":"object","properties":{"diff_per_night":{"type":"number","description":"channel public − direct public; negative = the channel undercuts direct."},"diff_pct":{"type":"number","description":"As a percentage of the direct public rate."},"cheaper":{"type":"string","enum":["direct","channel","equal"]}},"required":["diff_per_night","diff_pct","cheaper"],"additionalProperties":false},"rates":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Rate plans the channel returned."},"skipped":{"nullable":true,"type":"string"},"source":{"nullable":true,"type":"string","enum":["live","cached"]},"cache_age_sec":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"credits":{"type":"number"}},"required":["channel","id","confidence","status","public","member","vs_direct","rates","skipped","source","cache_age_sec","credits"],"additionalProperties":false}},"cheapest":{"nullable":true,"description":"The lowest PUBLIC per-night figure across direct and the shopped channels.","type":"object","properties":{"channel":{"type":"string","enum":["direct","booking","expedia","hotelscom","ctrip","agoda"]},"per_night":{"type":"number"},"basis":{"type":"string","enum":["after_tax","native"]}},"required":["channel","per_night","basis"],"additionalProperties":false},"verdict":{"type":"string","enum":["direct_lowest","ota_undercuts","equal","no_direct_rate","no_channel_rate"]},"max_undercut_pct":{"nullable":true,"description":"The deepest channel undercut of the direct public rate, in %; null when none undercuts.","type":"number"},"comparison_note":{"type":"string","description":"Which basis the comparison used, what it leaves out, and which channels were left uncompared for being in another currency."},"credits":{"type":"object","properties":{"direct":{"type":"number"},"channels":{"type":"number"},"ids":{"type":"number"},"total":{"type":"number"}},"required":["direct","channels","ids","total"],"additionalProperties":false},"shopped_at":{"type":"string"}},"required":["chain","hotel","checkin","los","occupancy","currency","direct","channels","cheapest","verdict","max_undercut_pct","comparison_note","credits","shopped_at"],"additionalProperties":false},"HotelIdsResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"ids":{"type":"object","properties":{"google":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"confidence":{"nullable":true,"description":"1 = set by hand; 0.9 = name + coordinates within 1 km; 0.85 = Google Hotels' own cross-source match (name score ≥ 0.6), 0.6 when its name score was 0.45–0.6; 0.5 = name only, or coordinates 1–3 km apart.","type":"number"},"method":{"nullable":true,"type":"string"},"matched_at":{"type":"string"}},"required":["id","confidence","method","matched_at"],"additionalProperties":false},"google_hotels":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"confidence":{"nullable":true,"description":"1 = set by hand; 0.9 = name + coordinates within 1 km; 0.85 = Google Hotels' own cross-source match (name score ≥ 0.6), 0.6 when its name score was 0.45–0.6; 0.5 = name only, or coordinates 1–3 km apart.","type":"number"},"method":{"nullable":true,"type":"string"},"matched_at":{"type":"string"}},"required":["id","confidence","method","matched_at"],"additionalProperties":false},"booking":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"confidence":{"nullable":true,"description":"1 = set by hand; 0.9 = name + coordinates within 1 km; 0.85 = Google Hotels' own cross-source match (name score ≥ 0.6), 0.6 when its name score was 0.45–0.6; 0.5 = name only, or coordinates 1–3 km apart.","type":"number"},"method":{"nullable":true,"type":"string"},"matched_at":{"type":"string"}},"required":["id","confidence","method","matched_at"],"additionalProperties":false},"expedia":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"confidence":{"nullable":true,"description":"1 = set by hand; 0.9 = name + coordinates within 1 km; 0.85 = Google Hotels' own cross-source match (name score ≥ 0.6), 0.6 when its name score was 0.45–0.6; 0.5 = name only, or coordinates 1–3 km apart.","type":"number"},"method":{"nullable":true,"type":"string"},"matched_at":{"type":"string"}},"required":["id","confidence","method","matched_at"],"additionalProperties":false},"hotelscom":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"confidence":{"nullable":true,"description":"1 = set by hand; 0.9 = name + coordinates within 1 km; 0.85 = Google Hotels' own cross-source match (name score ≥ 0.6), 0.6 when its name score was 0.45–0.6; 0.5 = name only, or coordinates 1–3 km apart.","type":"number"},"method":{"nullable":true,"type":"string"},"matched_at":{"type":"string"}},"required":["id","confidence","method","matched_at"],"additionalProperties":false},"ctrip":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"confidence":{"nullable":true,"description":"1 = set by hand; 0.9 = name + coordinates within 1 km; 0.85 = Google Hotels' own cross-source match (name score ≥ 0.6), 0.6 when its name score was 0.45–0.6; 0.5 = name only, or coordinates 1–3 km apart.","type":"number"},"method":{"nullable":true,"type":"string"},"matched_at":{"type":"string"}},"required":["id","confidence","method","matched_at"],"additionalProperties":false},"agoda":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"confidence":{"nullable":true,"description":"1 = set by hand; 0.9 = name + coordinates within 1 km; 0.85 = Google Hotels' own cross-source match (name score ≥ 0.6), 0.6 when its name score was 0.45–0.6; 0.5 = name only, or coordinates 1–3 km apart.","type":"number"},"method":{"nullable":true,"type":"string"},"matched_at":{"type":"string"}},"required":["id","confidence","method","matched_at"],"additionalProperties":false}},"required":["google","google_hotels","booking","expedia","hotelscom","ctrip","agoda"],"additionalProperties":false,"description":"google: a Places place id. google_hotels: Google's hotel entity id (the page whose partner links give every OTA id at once). The others: the hotel's id on that source — the `hotel_code` to use with chain=<source> on /shop, /calendar, /matrix, /watches."},"unresolved":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string","enum":["google","google_hotels","booking","expedia","hotelscom","ctrip","agoda"]},"reason":{"type":"string"}},"required":["source","reason"],"additionalProperties":false},"description":"Sources asked for and not mapped, and why. Booking has no automatic path."},"resolved_now":{"type":"array","items":{"type":"string","enum":["google","google_hotels","booking","expedia","hotelscom","ctrip","agoda"]},"description":"Sources looked up live on this call."},"credits":{"type":"number"}},"required":["chain","code","name","ids","unresolved","resolved_now","credits"],"additionalProperties":false},"OtaShopRequest":{"type":"object","properties":{"hotel_code":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,16}$"},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD. A date already past in every timezone (earlier than yesterday UTC) is rejected as invalid_request and never billed — no chain sells a stay that has started."},"los":{"default":1,"type":"integer","minimum":1,"maximum":30},"occupancy":{"default":{"adults":2,"children":[],"rooms":1},"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}}},"currency":{"default":"USD","type":"string","pattern":"^[A-Za-z]{3}$"},"rate_filters":{"type":"array","items":{"type":"string","enum":["bar","member","advance_purchase","package","corporate","aaa","senior","government","other"]}},"max_age":{"description":"Max acceptable cache age in seconds. Omitted: any cached answer within the cache TTL, at the cached rate. 0: always shop live, at the live rate. Not part of the cache key.","type":"integer","minimum":0,"maximum":9007199254740991},"raw":{"default":false,"description":"Also return the source's raw upstream bodies for this shop (forces a live call).","type":"boolean"}},"required":["hotel_code","checkin"]},"OtaShopResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"hotel":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"currency":{"nullable":true,"type":"string"}},"required":["code"],"additionalProperties":false},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"]},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"rates":{"type":"array","items":{"type":"object","properties":{"room_code":{"type":"string"},"room_name":{"nullable":true,"type":"string"},"bed_type":{"nullable":true,"type":"string"},"max_persons":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rate_code":{"nullable":true,"type":"string"},"rate_name":{"nullable":true,"type":"string"},"rate_type":{"default":"other","type":"string","enum":["bar","member","advance_purchase","package","corporate","aaa","senior","government","other"]},"member_only":{"default":false,"type":"boolean"},"refundable":{"nullable":true,"type":"boolean"},"cancel_deadline":{"nullable":true,"type":"string"},"meal_plan":{"default":"room_only","type":"string","enum":["room_only","breakfast","half_board","full_board","all_inclusive"]},"price_per_night":{"description":"Nightly rate in this chain's NATIVE tax basis — read `tax_included` to know which. Averaged over the stay when nightly rates differ. Not comparable across chains; use price_per_night_after_tax for that.","nullable":true,"type":"number"},"price_per_night_after_tax":{"description":"Nightly rate INCLUDING taxes and fees — the cross-chain comparable figure. Derived per chain from source fields verified against live payloads. Null when the source carries no tax data (never estimated).","nullable":true,"type":"number"},"price_total":{"description":"Price for the WHOLE stay (los nights), not per night.","nullable":true,"type":"number"},"taxes_fees_total":{"description":"Taxes + fees for the whole stay, when the chain itemises them. Null otherwise.","nullable":true,"type":"number"},"tax_included":{"description":"Whether price_per_night already includes taxes/fees. Measured per chain: true for hyatt, ihg, melia, premierinn, radisson; false for hilton, marriott, choice, bestwestern, iprefer, wyndham.","nullable":true,"type":"boolean"},"currency":{"default":"USD","type":"string"},"rooms_left":{"nullable":true,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"points_per_night":{"description":"Award cost in points PER NIGHT (not the whole stay).","nullable":true,"type":"number"},"points_total":{"description":"Award cost in points for the whole stay (points_per_night x los when the source gives only one of the two).","nullable":true,"type":"number"},"cash_copay":{"description":"Cash required PER NIGHT on top of the points, for a points+cash award. A positive value means this is NOT a free award night.","nullable":true,"type":"number"}},"required":["room_code","rate_type","member_only","meal_plan","currency"],"additionalProperties":false}},"lowest":{"default":{},"description":"Lowest NIGHTLY figures across `rates`. Keys: `bar` (lowest public rate), `member` (lowest member-only rate), `bar_after_tax` and `member_after_tax` (the same two on an after-tax basis). Compare across chains with the *_after_tax keys only — `bar`/`member` are in each chain's native tax basis. A key is absent when no rate supplies it; zero/negative placeholder prices are excluded.","type":"object","additionalProperties":{"type":"number"}},"shopped_at":{"type":"string"},"source":{"default":"live","type":"string","enum":["live","cached"]},"cache_age_sec":{"default":0,"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"raw":{"description":"The upstream bodies behind this answer, when `raw` was asked: text, capped at 400 KB each.","nullable":true,"type":"array","items":{"type":"object","properties":{"what":{"type":"string"},"status":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"body":{"type":"string"}},"required":["what","status","bytes","body"],"additionalProperties":false}}},"required":["chain","hotel","checkin","los","occupancy","status","rates","lowest","shopped_at","source","cache_age_sec"],"additionalProperties":false},"CalendarAreaResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"kind":{"type":"string","enum":["cash","award"]},"nights":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"required":["from","to"],"additionalProperties":false},"los":{"type":"number","enum":[1]},"occupancy":{"type":"object","properties":{"adults":{"default":2,"type":"integer","minimum":1,"maximum":8},"children":{"default":[],"maxItems":8,"type":"array","items":{"type":"integer","minimum":0,"maximum":17}},"rooms":{"default":1,"type":"integer","minimum":1,"maximum":8}},"required":["adults","children","rooms"],"additionalProperties":false},"currency":{"type":"string"},"center":{"nullable":true,"type":"object","properties":{"label":{"nullable":true,"type":"string"},"latitude":{"nullable":true,"type":"number"},"longitude":{"nullable":true,"type":"number"}},"additionalProperties":false},"hotels_found":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hotels_searched":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"heatmap":{"type":"array","items":{"type":"object","properties":{"night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"hotels_known":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Hotels with a fresh cell for this night."},"hotels_available":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Of those, open with a figure to rank by: a copay-free award (award) or a public bar (cash)."},"min":{"nullable":true,"description":"The cheapest figure that night across the hotels: points (award) or bar in `currency` (cash).","type":"number"},"min_hotel_code":{"nullable":true,"type":"string"},"median":{"nullable":true,"description":"Median of the same figure over the available hotels — how expensive the night is, not just its outlier.","type":"number"},"best_cpp":{"description":"award only: the best cents-per-point that night, where cells carry cpp.","nullable":true,"type":"number"},"best_cpp_hotel_code":{"nullable":true,"type":"string"},"credits":{"type":"number","description":"What this night costs in this answer: available hotels × the chain's cache-hit night rate, capped at 1."}},"required":["night","hotels_known","hotels_available","min","min_hotel_code","median","credits"],"additionalProperties":false},"description":"One row per checkin night in the window, in date order — including nights nothing is known about (counts 0, figures null)."},"hotels":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"address":{"nullable":true,"type":"string"},"city":{"nullable":true,"type":"string"},"region":{"nullable":true,"type":"string"},"country":{"nullable":true,"type":"string"},"postal_code":{"nullable":true,"type":"string"},"latitude":{"nullable":true,"type":"number"},"longitude":{"nullable":true,"type":"number"},"distance_km":{"nullable":true,"type":"number"},"rating":{"nullable":true,"type":"number"},"rating_count":{"nullable":true,"type":"number"},"nights_known":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"nights_available":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"min":{"nullable":true,"description":"This hotel's cheapest night in the window (points or bar).","type":"number"},"min_night":{"nullable":true,"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"required":["code","nights_known","nights_available","min","min_night"],"additionalProperties":false},"description":"The searched hotels, cheapest night first; hotels with nothing available trail by distance."},"freshness":{"type":"object","properties":{"max_age":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells_known":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells_served":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells_missing":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"oldest_served_at":{"nullable":true,"type":"string"}},"required":["max_age","cells_known","cells_served","cells_missing","oldest_served_at"],"additionalProperties":false},"job":{"nullable":true,"description":"The /matrix fill job, per `fill`; poll GET /jobs/{id} and re-run when done.","type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["matrix","ids"],"description":"matrix: cells (GET /jobs/{id}/cells, /export). ids: an id-map resolution job (POST /hotels/ids/jobs) — read the results from GET /hotels/{chain}/{code}/ids."},"status":{"type":"string","enum":["queued","running","done","failed","cancelled"]},"cells_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tasks_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Upstream calls the planner split the job into — fewer than cells where a chain has a native batch endpoint."},"tasks_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tasks_failed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"credits_reserved":{"type":"number","description":"Worst case quoted at creation (every cell shopped live). Checked against the key's quota before the job is accepted."},"credits_spent":{"type":"number","description":"What the tasks actually cost so far: cells served from cache bill the cache-hit rate."},"created_at":{"type":"string"},"finished_at":{"nullable":true,"type":"string"},"webhook_url":{"nullable":true,"type":"string"}},"required":["id","kind","status","cells_total","cells_done","tasks_total","tasks_done","tasks_failed","credits_reserved","credits_spent","created_at","finished_at","webhook_url"],"additionalProperties":false},"credits":{"type":"object","properties":{"lookups":{"type":"number","description":"The place lookup at the GET /hotels rate (cached 24 h)."},"nights":{"type":"number","description":"Σ per night min(hotels_available × cache-hit night rate, 1), floor 1 — PRICING.md §12."},"fill_reserved":{"type":"number"}},"required":["lookups","nights","fill_reserved"],"additionalProperties":false},"shopped_at":{"type":"string"}},"required":["chain","kind","nights","los","occupancy","currency","center","hotels_found","hotels_searched","heatmap","hotels","freshness","job","credits","shopped_at"],"additionalProperties":false},"AreaResponse":{"type":"object","properties":{"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"kind":{"type":"string","enum":["cash","award"]},"checkin":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"center":{"nullable":true,"type":"object","properties":{"label":{"nullable":true,"type":"string"},"latitude":{"nullable":true,"type":"number"},"longitude":{"nullable":true,"type":"number"}},"additionalProperties":false},"hotels_found":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Hotels the chain returned in the radius."},"hotels_searched":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Of those, the nearest max_hotels that were priced."},"hotels":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"nullable":true,"type":"string"},"brand":{"nullable":true,"type":"string"},"address":{"nullable":true,"type":"string"},"city":{"nullable":true,"type":"string"},"region":{"nullable":true,"type":"string"},"country":{"nullable":true,"type":"string"},"postal_code":{"nullable":true,"type":"string"},"latitude":{"nullable":true,"type":"number"},"longitude":{"nullable":true,"type":"number"},"distance_km":{"nullable":true,"type":"number"},"rating":{"nullable":true,"type":"number"},"rating_count":{"nullable":true,"type":"number"},"cell":{"nullable":true,"description":"This hotel's cell for the stay, when served now; null while the job fills it.","type":"object","properties":{"hotel_code":{"type":"string"},"night":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"los":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"status":{"type":"string","enum":["available","sold_out","closed","restricted_los","not_found","error"],"description":"kind award: available when ANY award is sold; a cell can be available with lowest_points null when only a points+cash award exists (see lowest_points_cash)."},"min_los":{"description":"Minimum nights the hotel requires for this checkin, when the chain states it (today: IHG). null = the chain did not say — NOT 'no minimum'. Only meaningful with status restricted_los.","nullable":true,"type":"integer","minimum":1,"maximum":9007199254740991},"lowest":{"default":{},"description":"Same keys as ShopResponse.lowest: `bar`, `bar_after_tax`, `member`. Present on both kinds.","type":"object","additionalProperties":{"type":"number"}},"lowest_points":{"description":"Lowest COPAY-FREE award per night — the same figure whether the cell came from a shop or from the chain's calendar.","nullable":true,"type":"number"},"lowest_points_cash":{"description":"Points per night of the cheapest points+cash award (shop-sourced cells only; calendars do not carry it).","nullable":true,"type":"number"},"cash_copay":{"description":"Co-pay per night of `lowest_points_cash`.","nullable":true,"type":"number"},"cpp":{"description":"Cents per point of the copay-free award, over the night's lowest purchasable cash leg.","nullable":true,"type":"number"},"cpp_points_cash":{"nullable":true,"type":"number"},"currency":{"nullable":true,"type":"string"},"observed_at":{"type":"string","description":"When this cell was last refreshed from the chain."},"cache_age_sec":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"from_default_occupancy":{"description":"kind award only: this cell was served from the default-occupancy (2 adults, 1 room) cell because the exact occupancy had none; points rarely vary by occupancy.","type":"boolean"},"certs":{"description":"kind award only, present when non-empty: the free-night certificates that would cover this night's copay-free award, cheapest first, each with the top-up it needs. Marriott 35k/40k/50k/85k (top-up ≤ 15k), Hilton free night (any standard night), IHG 40k (top-up unlimited). Hyatt certificates are by category, which cells do not carry, so none is flagged. Judged at read time from program terms as of 2026-10-09.","type":"array","items":{"type":"object","properties":{"cert":{"type":"string","enum":["marriott_35k","marriott_40k","marriott_50k","marriott_85k","hilton_free_night","ihg_40k"]},"top_up":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Points the member adds on top of the certificate for this night; 0 = the certificate alone."}},"required":["cert","top_up"],"additionalProperties":false}}},"required":["hotel_code","night","los","status","lowest","observed_at","cache_age_sec"],"additionalProperties":false}},"required":["code","cell"],"additionalProperties":false},"description":"Ranked: cells served now first, by lowest points (award) or lowest bar (cash), then unpriced hotels by distance."},"cells_served":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"job":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["matrix","ids"],"description":"matrix: cells (GET /jobs/{id}/cells, /export). ids: an id-map resolution job (POST /hotels/ids/jobs) — read the results from GET /hotels/{chain}/{code}/ids."},"status":{"type":"string","enum":["queued","running","done","failed","cancelled"]},"cells_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cells_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tasks_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Upstream calls the planner split the job into — fewer than cells where a chain has a native batch endpoint."},"tasks_done":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"tasks_failed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"credits_reserved":{"type":"number","description":"Worst case quoted at creation (every cell shopped live). Checked against the key's quota before the job is accepted."},"credits_spent":{"type":"number","description":"What the tasks actually cost so far: cells served from cache bill the cache-hit rate."},"created_at":{"type":"string"},"finished_at":{"nullable":true,"type":"string"},"webhook_url":{"nullable":true,"type":"string"}},"required":["id","kind","status","cells_total","cells_done","tasks_total","tasks_done","tasks_failed","credits_reserved","credits_spent","created_at","finished_at","webhook_url"],"additionalProperties":false},"shopped_at":{"type":"string"}},"required":["chain","kind","checkin","los","center","hotels_found","hotels_searched","hotels","cells_served","job","shopped_at"],"additionalProperties":false},"ErrorEnvelope":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","auth_failed","quota_exceeded","rate_limited","source_unavailable","hotel_not_found","upstream_timeout"]},"message":{"type":"string"},"chain":{"type":"string","enum":["marriott","hilton","hyatt","ihg","wyndham","choice","radisson","iprefer","melia","premierinn","bestwestern","booking","expedia","hotelscom","ctrip","agoda"]},"request_id":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}}